FUtilities.m 14 KB

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