FUtilities.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <FirebaseCore/FIRLogger.h>
  17. #import "FUtilities.h"
  18. #import "FStringUtilities.h"
  19. #import "FConstants.h"
  20. #import "FAtomicNumber.h"
  21. #define ARC4RANDOM_MAX 0x100000000
  22. #define INTEGER_32_MIN (-2147483648)
  23. #define INTEGER_32_MAX 2147483647
  24. #pragma mark -
  25. #pragma mark C functions
  26. static FLogLevel logLevel = FLogLevelInfo; // Default log level is info
  27. static NSMutableDictionary* options = nil;
  28. BOOL FFIsLoggingEnabled(FLogLevel level) {
  29. return level >= logLevel;
  30. }
  31. void firebaseJobsTroll(void) {
  32. FFLog(@"I-RDB095001", @"password super secret; JFK conspiracy; Hello there! Having fun digging through Firebase? We're always hiring! jobs@firebase.com");
  33. }
  34. #pragma mark -
  35. #pragma mark Private property and singleton specification
  36. @interface FUtilities() {
  37. }
  38. @property (nonatomic, strong) FAtomicNumber* localUid;
  39. + (FUtilities*)singleton;
  40. @end
  41. @implementation FUtilities
  42. @synthesize localUid;
  43. - (id)init
  44. {
  45. self = [super init];
  46. if (self) {
  47. self.localUid = [[FAtomicNumber alloc] init];
  48. }
  49. return self;
  50. }
  51. // TODO: We really want to be able to set the log level
  52. + (void) setLoggingEnabled:(BOOL)enabled {
  53. logLevel = enabled ? FLogLevelDebug : FLogLevelInfo;
  54. }
  55. + (BOOL) getLoggingEnabled {
  56. return logLevel == FLogLevelDebug;
  57. }
  58. + (FUtilities*) singleton
  59. {
  60. static dispatch_once_t pred = 0;
  61. __strong static id _sharedObject = nil;
  62. dispatch_once(&pred, ^{
  63. _sharedObject = [[self alloc] init]; // or some other init method
  64. });
  65. return _sharedObject;
  66. }
  67. // Refactor as a category of NSString
  68. + (NSArray *) splitString:(NSString *) str intoMaxSize:(const unsigned int) size {
  69. if(str.length <= size) {
  70. return [NSArray arrayWithObject:str];
  71. }
  72. NSMutableArray* dataSegs = [[NSMutableArray alloc] init];
  73. for(int c = 0; c < str.length; c += size) {
  74. if (c + size > str.length) {
  75. int rangeStart = c;
  76. unsigned long rangeLength = size - ((c + size) - str.length);
  77. [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
  78. }
  79. else {
  80. int rangeStart = c;
  81. int rangeLength = size;
  82. [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
  83. }
  84. }
  85. return dataSegs;
  86. }
  87. + (NSNumber *) LUIDGenerator {
  88. FUtilities* f = [FUtilities singleton];
  89. return [f.localUid getAndIncrement];
  90. }
  91. + (NSString *) decodePath:(NSString *)pathString {
  92. NSMutableArray* decodedPieces = [[NSMutableArray alloc] init];
  93. NSArray* pieces = [pathString componentsSeparatedByString:@"/"];
  94. for (NSString* piece in pieces) {
  95. if (piece.length > 0) {
  96. [decodedPieces addObject:[FStringUtilities urlDecoded:piece]];
  97. }
  98. }
  99. return [NSString stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
  100. }
  101. + (FParsedUrl *) parseUrl:(NSString *)url {
  102. NSString* original = url;
  103. //NSURL* n = [[NSURL alloc] initWithString:url]
  104. NSString* host;
  105. NSString* namespace;
  106. bool secure;
  107. NSString* scheme = nil;
  108. FPath* path = nil;
  109. NSRange colonIndex = [url rangeOfString:@"//"];
  110. if (colonIndex.location != NSNotFound) {
  111. scheme = [url substringToIndex:colonIndex.location - 1];
  112. url = [url substringFromIndex:colonIndex.location + 2];
  113. }
  114. NSInteger slashIndex = [url rangeOfString:@"/"].location;
  115. if (slashIndex == NSNotFound) {
  116. slashIndex = url.length;
  117. }
  118. host = [[url substringToIndex:slashIndex] lowercaseString];
  119. if (slashIndex >= url.length) {
  120. url = @"";
  121. } else {
  122. url = [url substringFromIndex:slashIndex + 1];
  123. }
  124. NSArray *parts = [host componentsSeparatedByString:@"."];
  125. if([parts count] == 3) {
  126. NSInteger colonIndex = [[parts objectAtIndex:2] rangeOfString:@":"].location;
  127. if (colonIndex != NSNotFound) {
  128. // we have a port, use the provided scheme
  129. secure = [scheme isEqualToString:@"https"];
  130. } else {
  131. secure = YES;
  132. }
  133. namespace = [[parts objectAtIndex:0] lowercaseString];
  134. NSString* pathString = [self decodePath:[NSString stringWithFormat:@"/%@", url]];
  135. path = [[FPath alloc] initWith:pathString];
  136. }
  137. else {
  138. [NSException raise:@"No Firebase database specified." format:@"No Firebase database found for input: %@", url];
  139. }
  140. FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:secure withNamespace:namespace];
  141. FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", original, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
  142. FParsedUrl* parsedUrl = [[FParsedUrl alloc] init];
  143. parsedUrl.repoInfo = repoInfo;
  144. parsedUrl.path = path;
  145. return parsedUrl;
  146. }
  147. /*
  148. case str: JString => priString + "string:" + str.s;
  149. case bool: JBool => priString + "boolean:" + bool.value;
  150. case double: JDouble => priString + "number:" + double.num;
  151. case int: JInt => priString + "number:" + int.num;
  152. case _ => {
  153. error("Leaf node has value '" + data.value + "' of invalid type '" + data.value.getClass.toString + "'");
  154. "";
  155. }
  156. */
  157. + (NSString *) getJavascriptType:(id)obj {
  158. if ([obj isKindOfClass:[NSDictionary class]]) {
  159. return kJavaScriptObject;
  160. } else if([obj isKindOfClass:[NSString class]]) {
  161. return kJavaScriptString;
  162. }
  163. else if ([obj isKindOfClass:[NSNumber class]]) {
  164. // We used to just compare to @encode(BOOL) as suggested at
  165. // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but on arm64, @encode(BOOL) returns "B"
  166. // instead of "c" even though objCType still returns 'c' (signed char). So check both.
  167. if(strcmp([obj objCType], @encode(BOOL)) == 0 ||
  168. strcmp([obj objCType], @encode(signed char)) == 0) {
  169. return kJavaScriptBoolean;
  170. }
  171. else {
  172. return kJavaScriptNumber;
  173. }
  174. }
  175. else {
  176. return kJavaScriptNull;
  177. }
  178. }
  179. + (NSError *) errorForStatus:(NSString *)status andReason:(NSString *)reason {
  180. static dispatch_once_t pred = 0;
  181. __strong static NSDictionary* errorMap = nil;
  182. __strong static NSDictionary* errorCodes = nil;
  183. dispatch_once(&pred, ^{
  184. errorMap = @{
  185. @"permission_denied": @"Permission Denied",
  186. @"unavailable": @"Service is unavailable",
  187. kFErrorWriteCanceled: @"Write cancelled by user"
  188. };
  189. errorCodes = @{
  190. @"permission_denied": @1,
  191. @"unavailable": @2,
  192. kFErrorWriteCanceled: @3
  193. };
  194. });
  195. if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
  196. return nil;
  197. } else {
  198. NSInteger code;
  199. NSString* desc = nil;
  200. if (reason) {
  201. desc = reason;
  202. } else if ([errorMap objectForKey:status] != nil) {
  203. desc = [errorMap objectForKey:status];
  204. } else {
  205. desc = status;
  206. }
  207. if ([errorCodes objectForKey:status] != nil) {
  208. NSNumber* num = [errorCodes objectForKey:status];
  209. code = [num integerValue];
  210. } else {
  211. // XXX what to do here?
  212. code = 9999;
  213. }
  214. return [[NSError alloc] initWithDomain:kFErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: desc}];
  215. }
  216. }
  217. + (NSNumber *) intForString:(NSString *)string {
  218. static NSCharacterSet *notDigits = nil;
  219. if (!notDigits) {
  220. notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
  221. }
  222. if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
  223. NSInteger num;
  224. NSScanner* scanner = [NSScanner scannerWithString:string];
  225. if ([scanner scanInteger:&num]) {
  226. return [NSNumber numberWithInteger:num];
  227. }
  228. }
  229. return nil;
  230. }
  231. + (NSString *) ieee754StringForNumber:(NSNumber *)val {
  232. double d = [val doubleValue];
  233. NSData* data = [NSData dataWithBytes:&d length:sizeof(double)];
  234. NSMutableString* str = [[NSMutableString alloc] init];
  235. const unsigned char* buffer = (const unsigned char*)[data bytes];
  236. for (int i = 0; i < data.length; i++) {
  237. unsigned char byte = buffer[7 - i];
  238. [str appendFormat:@"%02x", byte];
  239. }
  240. return str;
  241. }
  242. static inline BOOL tryParseStringToInt(__unsafe_unretained NSString* str, NSInteger* integer) {
  243. // First do some cheap checks (NOTE: The below checks are significantly faster than an equivalent regex :-( ).
  244. NSUInteger length = str.length;
  245. if (length > 11 || length == 0) {
  246. return NO;
  247. }
  248. long long value = 0;
  249. BOOL negative = NO;
  250. NSUInteger i = 0;
  251. if ([str characterAtIndex:0] == '-') {
  252. if (length == 1) {
  253. return NO;
  254. }
  255. negative = YES;
  256. i = 1;
  257. }
  258. for(; i < length; i++) {
  259. unichar c = [str characterAtIndex:i];
  260. // Must be a digit, or '-' if it's the first char.
  261. if (c < '0' || c > '9') {
  262. return NO;
  263. } else {
  264. int charValue = c - '0';
  265. value = value*10 + charValue;
  266. }
  267. }
  268. value = (negative) ? -value : value;
  269. if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
  270. return NO;
  271. } else {
  272. *integer = (NSInteger)value;
  273. return YES;
  274. }
  275. }
  276. + (NSString *) maxName {
  277. static dispatch_once_t once;
  278. static NSString *maxName;
  279. dispatch_once(&once, ^{
  280. maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
  281. });
  282. return maxName;
  283. }
  284. + (NSString *) minName {
  285. static dispatch_once_t once;
  286. static NSString *minName;
  287. dispatch_once(&once, ^{
  288. minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
  289. });
  290. return minName;
  291. }
  292. + (NSComparisonResult) compareKey:(NSString *)a toKey:(NSString *)b {
  293. if (a == b) {
  294. return NSOrderedSame;
  295. } else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
  296. return NSOrderedAscending;
  297. } else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
  298. return NSOrderedDescending;
  299. } else {
  300. NSInteger aAsInt, bAsInt;
  301. if (tryParseStringToInt(a, &aAsInt)) {
  302. if (tryParseStringToInt(b, &bAsInt)) {
  303. if (aAsInt > bAsInt) {
  304. return NSOrderedDescending;
  305. } else if (aAsInt < bAsInt) {
  306. return NSOrderedAscending;
  307. } else if (a.length > b.length) {
  308. return NSOrderedDescending;
  309. } else if (a.length < b.length) {
  310. return NSOrderedAscending;
  311. } else {
  312. return NSOrderedSame;
  313. }
  314. } else {
  315. return (NSComparisonResult) NSOrderedAscending;
  316. }
  317. } else if (tryParseStringToInt(b, &bAsInt)) {
  318. return (NSComparisonResult) NSOrderedDescending;
  319. } else {
  320. // Perform literal character by character search to prevent a > b && b > a issues.
  321. // Note that calling -(NSString *)decomposedStringWithCanonicalMapping also works.
  322. return [a compare:b options:NSLiteralSearch];
  323. }
  324. }
  325. }
  326. + (NSComparator) keyComparator {
  327. return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
  328. return [FUtilities compareKey:a toKey:b];
  329. };
  330. }
  331. + (NSComparator) stringComparator {
  332. return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
  333. return [a compare:b];
  334. };
  335. }
  336. + (double) randomDouble {
  337. return ((double) arc4random() / ARC4RANDOM_MAX);
  338. }
  339. @end