FUtilities.m 12 KB

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