FUtilities.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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 "FirebaseDatabase/Sources/Utilities/FUtilities.h"
  17. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  18. #import "FirebaseDatabase/Sources/Constants/FConstants.h"
  19. #import "FirebaseDatabase/Sources/Utilities/FAtomicNumber.h"
  20. #import "FirebaseDatabase/Sources/Utilities/FStringUtilities.h"
  21. #define ARC4RANDOM_MAX 0x100000000
  22. #pragma mark -
  23. #pragma mark C functions
  24. FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]";
  25. static FLogLevel logLevel = FLogLevelInfo; // Default log level is info
  26. static NSMutableDictionary *options = nil;
  27. BOOL FFIsLoggingEnabled(FLogLevel level) { return level >= logLevel; }
  28. void firebaseJobsTroll(void) {
  29. FFLog(@"I-RDB095001",
  30. @"password super secret; JFK conspiracy; Hello there! Having fun "
  31. @"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. self = [super init];
  44. if (self) {
  45. self.localUid = [[FAtomicNumber alloc] init];
  46. }
  47. return self;
  48. }
  49. // TODO: We really want to be able to set the log level
  50. + (void)setLoggingEnabled:(BOOL)enabled {
  51. logLevel = enabled ? FLogLevelDebug : FLogLevelInfo;
  52. }
  53. + (BOOL)getLoggingEnabled {
  54. return logLevel == FLogLevelDebug;
  55. }
  56. + (FUtilities *)singleton {
  57. static dispatch_once_t pred = 0;
  58. __strong static id _sharedObject = nil;
  59. dispatch_once(&pred, ^{
  60. _sharedObject = [[self alloc] init]; // or some other init method
  61. });
  62. return _sharedObject;
  63. }
  64. // Refactor as a category of NSString
  65. + (NSArray *)splitString:(NSString *)str intoMaxSize:(const unsigned int)size {
  66. if (str.length <= size) {
  67. return [NSArray arrayWithObject:str];
  68. }
  69. NSMutableArray *dataSegs = [[NSMutableArray alloc] init];
  70. for (int c = 0; c < str.length; c += size) {
  71. if (c + size > str.length) {
  72. int rangeStart = c;
  73. unsigned long rangeLength = size - ((c + size) - str.length);
  74. [dataSegs
  75. addObject:[str substringWithRange:NSMakeRange(rangeStart,
  76. rangeLength)]];
  77. } else {
  78. int rangeStart = c;
  79. int rangeLength = size;
  80. [dataSegs
  81. addObject:[str substringWithRange:NSMakeRange(rangeStart,
  82. 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
  100. stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
  101. }
  102. + (NSString *)extractPathFromUrlString:(NSString *)url {
  103. NSString *path = url;
  104. NSRange schemeIndex = [path rangeOfString:@"//"];
  105. if (schemeIndex.location != NSNotFound) {
  106. path = [path substringFromIndex:schemeIndex.location + 2];
  107. }
  108. NSUInteger pathIndex = [path rangeOfString:@"/"].location;
  109. if (pathIndex != NSNotFound) {
  110. path = [path substringFromIndex:pathIndex + 1];
  111. } else {
  112. path = @"";
  113. }
  114. NSUInteger queryParamIndex = [path rangeOfString:@"?"].location;
  115. if (queryParamIndex != NSNotFound) {
  116. path = [path substringToIndex:queryParamIndex];
  117. }
  118. return path;
  119. }
  120. + (FParsedUrl *)parseUrl:(NSString *)url {
  121. // For backwards compatibility, support URLs without schemes on iOS.
  122. if (![url containsString:@"://"]) {
  123. url = [@"http://" stringByAppendingString:url];
  124. }
  125. NSString *originalPathString = [self extractPathFromUrlString:url];
  126. // Sanitize the database URL by removing the path component, which may
  127. // contain invalid URL characters.
  128. NSString *sanitizedUrlWithoutPath =
  129. [url stringByReplacingOccurrencesOfString:originalPathString
  130. withString:@""];
  131. NSURLComponents *urlComponents =
  132. [NSURLComponents componentsWithString:sanitizedUrlWithoutPath];
  133. if (!urlComponents) {
  134. [NSException raise:@"Failed to parse database URL"
  135. format:@"Failed to parse database URL: %@", url];
  136. }
  137. NSString *host = [urlComponents.host lowercaseString];
  138. NSString *namespace;
  139. bool secure;
  140. if (urlComponents.port != nil) {
  141. secure = [urlComponents.scheme isEqualToString:@"https"] ||
  142. [urlComponents.scheme isEqualToString:@"wss"];
  143. host = [host stringByAppendingFormat:@":%@", urlComponents.port];
  144. } else {
  145. secure = YES;
  146. };
  147. NSArray *parts = [urlComponents.host componentsSeparatedByString:@"."];
  148. if ([parts count] == 3) {
  149. namespace = [parts[0] lowercaseString];
  150. } else {
  151. // Attempt to extract namespace from "ns" query param.
  152. NSArray *queryItems = urlComponents.queryItems;
  153. for (NSURLQueryItem *item in queryItems) {
  154. if ([item.name isEqualToString:@"ns"]) {
  155. namespace = item.value;
  156. break;
  157. }
  158. }
  159. if (!namespace) {
  160. namespace = [parts[0] lowercaseString];
  161. }
  162. }
  163. NSString *pathString = [self
  164. decodePath:[NSString stringWithFormat:@"/%@", originalPathString]];
  165. FPath *path = [[FPath alloc] initWith:pathString];
  166. FRepoInfo *repoInfo = [[FRepoInfo alloc] initWithHost:host
  167. isSecure:secure
  168. withNamespace:namespace];
  169. FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)",
  170. url, [repoInfo description], [repoInfo connectionURL],
  171. repoInfo.namespace, [path description]);
  172. FParsedUrl *parsedUrl = [[FParsedUrl alloc] init];
  173. parsedUrl.repoInfo = repoInfo;
  174. parsedUrl.path = path;
  175. return parsedUrl;
  176. }
  177. /*
  178. case str: JString => priString + "string:" + str.s;
  179. case bool: JBool => priString + "boolean:" + bool.value;
  180. case double: JDouble => priString + "number:" + double.num;
  181. case int: JInt => priString + "number:" + int.num;
  182. case _ => {
  183. error("Leaf node has value '" + data.value + "' of invalid type '" +
  184. data.value.getClass.toString + "'");
  185. "";
  186. }
  187. */
  188. + (NSString *)getJavascriptType:(id)obj {
  189. if ([obj isKindOfClass:[NSDictionary class]]) {
  190. return kJavaScriptObject;
  191. } else if ([obj isKindOfClass:[NSString class]]) {
  192. return kJavaScriptString;
  193. } else if ([obj isKindOfClass:[NSNumber class]]) {
  194. // We used to just compare to @encode(BOOL) as suggested at
  195. // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but
  196. // on arm64, @encode(BOOL) returns "B" instead of "c" even though
  197. // objCType still returns 'c' (signed char). So check both.
  198. if (strcmp([obj objCType], @encode(BOOL)) == 0 ||
  199. strcmp([obj objCType], @encode(signed char)) == 0) {
  200. return kJavaScriptBoolean;
  201. } else {
  202. return kJavaScriptNumber;
  203. }
  204. } else {
  205. return kJavaScriptNull;
  206. }
  207. }
  208. + (NSError *)errorForStatus:(NSString *)status andReason:(NSString *)reason {
  209. static dispatch_once_t pred = 0;
  210. __strong static NSDictionary *errorMap = nil;
  211. __strong static NSDictionary *errorCodes = nil;
  212. dispatch_once(&pred, ^{
  213. errorMap = @{
  214. @"permission_denied" : @"Permission Denied",
  215. @"unavailable" : @"Service is unavailable",
  216. kFErrorWriteCanceled : @"Write cancelled by user"
  217. };
  218. errorCodes = @{
  219. @"permission_denied" : @1,
  220. @"unavailable" : @2,
  221. kFErrorWriteCanceled : @3
  222. };
  223. });
  224. if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
  225. return nil;
  226. } else {
  227. NSInteger code;
  228. NSString *desc = nil;
  229. if (reason) {
  230. desc = reason;
  231. } else if ([errorMap objectForKey:status] != nil) {
  232. desc = [errorMap objectForKey:status];
  233. } else {
  234. desc = status;
  235. }
  236. if ([errorCodes objectForKey:status] != nil) {
  237. NSNumber *num = [errorCodes objectForKey:status];
  238. code = [num integerValue];
  239. } else {
  240. // XXX what to do here?
  241. code = 9999;
  242. }
  243. return [[NSError alloc]
  244. initWithDomain:kFErrorDomain
  245. code:code
  246. userInfo:@{NSLocalizedDescriptionKey : desc}];
  247. }
  248. }
  249. + (NSNumber *)intForString:(NSString *)string {
  250. static NSCharacterSet *notDigits = nil;
  251. if (!notDigits) {
  252. notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
  253. }
  254. if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
  255. NSInteger num;
  256. NSScanner *scanner = [NSScanner scannerWithString:string];
  257. if ([scanner scanInteger:&num]) {
  258. return [NSNumber numberWithInteger:num];
  259. }
  260. }
  261. return nil;
  262. }
  263. + (NSInteger)int32min {
  264. return INTEGER_32_MIN;
  265. }
  266. + (NSInteger)int32max {
  267. return INTEGER_32_MAX;
  268. }
  269. + (NSString *)ieee754StringForNumber:(NSNumber *)val {
  270. double d = [val doubleValue];
  271. NSData *data = [NSData dataWithBytes:&d length:sizeof(double)];
  272. NSMutableString *str = [[NSMutableString alloc] init];
  273. const unsigned char *buffer = (const unsigned char *)[data bytes];
  274. for (int i = 0; i < data.length; i++) {
  275. unsigned char byte = buffer[7 - i];
  276. [str appendFormat:@"%02x", byte];
  277. }
  278. return str;
  279. }
  280. + (BOOL)tryParseString:(NSString *)string asInt:(NSInteger *)integer {
  281. return tryParseStringToInt(string, integer);
  282. }
  283. static inline BOOL tryParseStringToInt(__unsafe_unretained NSString *str,
  284. NSInteger *integer) {
  285. // First do some cheap checks (NOTE: The below checks are significantly
  286. // faster than an equivalent regex :-( ).
  287. NSUInteger length = str.length;
  288. if (length > 11 || length == 0) {
  289. return NO;
  290. }
  291. long long value = 0;
  292. BOOL negative = NO;
  293. NSUInteger i = 0;
  294. if ([str characterAtIndex:0] == '-') {
  295. if (length == 1) {
  296. return NO;
  297. }
  298. negative = YES;
  299. i = 1;
  300. }
  301. for (; i < length; i++) {
  302. unichar c = [str characterAtIndex:i];
  303. // Must be a digit, or '-' if it's the first char.
  304. if (c < '0' || c > '9') {
  305. return NO;
  306. } else {
  307. int charValue = c - '0';
  308. value = value * 10 + charValue;
  309. }
  310. }
  311. value = (negative) ? -value : value;
  312. if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
  313. return NO;
  314. } else {
  315. *integer = (NSInteger)value;
  316. return YES;
  317. }
  318. }
  319. + (NSString *)maxName {
  320. static dispatch_once_t once;
  321. static NSString *maxName;
  322. dispatch_once(&once, ^{
  323. maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
  324. });
  325. return maxName;
  326. }
  327. + (NSString *)minName {
  328. static dispatch_once_t once;
  329. static NSString *minName;
  330. dispatch_once(&once, ^{
  331. minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
  332. });
  333. return minName;
  334. }
  335. + (NSComparisonResult)compareKey:(NSString *)a toKey:(NSString *)b {
  336. if (a == b) {
  337. return NSOrderedSame;
  338. } else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
  339. return NSOrderedAscending;
  340. } else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
  341. return NSOrderedDescending;
  342. } else {
  343. NSInteger aAsInt, bAsInt;
  344. if (tryParseStringToInt(a, &aAsInt)) {
  345. if (tryParseStringToInt(b, &bAsInt)) {
  346. if (aAsInt > bAsInt) {
  347. return NSOrderedDescending;
  348. } else if (aAsInt < bAsInt) {
  349. return NSOrderedAscending;
  350. } else if (a.length > b.length) {
  351. return NSOrderedDescending;
  352. } else if (a.length < b.length) {
  353. return NSOrderedAscending;
  354. } else {
  355. return NSOrderedSame;
  356. }
  357. } else {
  358. return (NSComparisonResult)NSOrderedAscending;
  359. }
  360. } else if (tryParseStringToInt(b, &bAsInt)) {
  361. return (NSComparisonResult)NSOrderedDescending;
  362. } else {
  363. // Perform literal character by character search to prevent a > b &&
  364. // b > a issues. Note that calling -(NSString
  365. // *)decomposedStringWithCanonicalMapping also works.
  366. return [a compare:b options:NSLiteralSearch];
  367. }
  368. }
  369. }
  370. + (NSComparator)keyComparator {
  371. return ^NSComparisonResult(__unsafe_unretained NSString *a,
  372. __unsafe_unretained NSString *b) {
  373. return [FUtilities compareKey:a toKey:b];
  374. };
  375. }
  376. + (NSComparator)stringComparator {
  377. return ^NSComparisonResult(__unsafe_unretained NSString *a,
  378. __unsafe_unretained NSString *b) {
  379. return [a compare:b];
  380. };
  381. }
  382. + (double)randomDouble {
  383. return ((double)arc4random() / ARC4RANDOM_MAX);
  384. }
  385. @end