FUtilities.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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/Extension/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 = @"[FirebaseDatabase]";
  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. NSRange lastMatch = [url rangeOfString:originalPathString
  129. options:NSBackwardsSearch];
  130. NSString *sanitizedUrlWithoutPath =
  131. (lastMatch.location != NSNotFound)
  132. ? [url substringToIndex:lastMatch.location]
  133. : url;
  134. NSURLComponents *urlComponents =
  135. [NSURLComponents componentsWithString:sanitizedUrlWithoutPath];
  136. if (!urlComponents) {
  137. [NSException raise:@"Failed to parse database URL"
  138. format:@"Failed to parse database URL: %@", url];
  139. }
  140. NSString *host = [urlComponents.host lowercaseString];
  141. NSString *namespace;
  142. bool secure;
  143. if (urlComponents.port != nil) {
  144. secure = [urlComponents.scheme isEqualToString:@"https"] ||
  145. [urlComponents.scheme isEqualToString:@"wss"];
  146. host = [host stringByAppendingFormat:@":%@", urlComponents.port];
  147. } else {
  148. secure = YES;
  149. };
  150. NSArray *parts = [urlComponents.host componentsSeparatedByString:@"."];
  151. if ([parts count] == 3) {
  152. namespace = [parts[0] lowercaseString];
  153. } else {
  154. // Attempt to extract namespace from "ns" query param.
  155. NSArray *queryItems = urlComponents.queryItems;
  156. for (NSURLQueryItem *item in queryItems) {
  157. if ([item.name isEqualToString:@"ns"]) {
  158. namespace = item.value;
  159. break;
  160. }
  161. }
  162. if (!namespace) {
  163. namespace = [parts[0] lowercaseString];
  164. }
  165. }
  166. NSString *pathString = [self
  167. decodePath:[NSString stringWithFormat:@"/%@", originalPathString]];
  168. FPath *path = [[FPath alloc] initWith:pathString];
  169. FRepoInfo *repoInfo = [[FRepoInfo alloc] initWithHost:host
  170. isSecure:secure
  171. withNamespace:namespace];
  172. FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)",
  173. url, [repoInfo description], [repoInfo connectionURL],
  174. repoInfo.namespace, [path description]);
  175. FParsedUrl *parsedUrl = [[FParsedUrl alloc] init];
  176. parsedUrl.repoInfo = repoInfo;
  177. parsedUrl.path = path;
  178. return parsedUrl;
  179. }
  180. /*
  181. case str: JString => priString + "string:" + str.s;
  182. case bool: JBool => priString + "boolean:" + bool.value;
  183. case double: JDouble => priString + "number:" + double.num;
  184. case int: JInt => priString + "number:" + int.num;
  185. case _ => {
  186. error("Leaf node has value '" + data.value + "' of invalid type '" +
  187. data.value.getClass.toString + "'");
  188. "";
  189. }
  190. */
  191. + (NSString *)getJavascriptType:(id)obj {
  192. if ([obj isKindOfClass:[NSDictionary class]]) {
  193. return kJavaScriptObject;
  194. } else if ([obj isKindOfClass:[NSString class]]) {
  195. return kJavaScriptString;
  196. } else if ([obj isKindOfClass:[NSNumber class]]) {
  197. // We used to just compare to @encode(BOOL) as suggested at
  198. // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but
  199. // on arm64, @encode(BOOL) returns "B" instead of "c" even though
  200. // objCType still returns 'c' (signed char). So check both.
  201. if (strcmp([obj objCType], @encode(BOOL)) == 0 ||
  202. strcmp([obj objCType], @encode(signed char)) == 0) {
  203. return kJavaScriptBoolean;
  204. } else {
  205. return kJavaScriptNumber;
  206. }
  207. } else {
  208. return kJavaScriptNull;
  209. }
  210. }
  211. + (NSError *)errorForStatus:(NSString *)status andReason:(NSString *)reason {
  212. static dispatch_once_t pred = 0;
  213. __strong static NSDictionary *errorMap = nil;
  214. __strong static NSDictionary *errorCodes = nil;
  215. dispatch_once(&pred, ^{
  216. errorMap = @{
  217. @"permission_denied" : @"Permission Denied",
  218. @"unavailable" : @"Service is unavailable",
  219. kFErrorWriteCanceled : @"Write cancelled by user"
  220. };
  221. errorCodes = @{
  222. @"permission_denied" : @1,
  223. @"unavailable" : @2,
  224. kFErrorWriteCanceled : @3
  225. };
  226. });
  227. if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
  228. return nil;
  229. } else {
  230. NSInteger code;
  231. NSString *desc = nil;
  232. if (reason) {
  233. desc = reason;
  234. } else if ([errorMap objectForKey:status] != nil) {
  235. desc = [errorMap objectForKey:status];
  236. } else {
  237. desc = status;
  238. }
  239. if ([errorCodes objectForKey:status] != nil) {
  240. NSNumber *num = [errorCodes objectForKey:status];
  241. code = [num integerValue];
  242. } else {
  243. // XXX what to do here?
  244. code = 9999;
  245. }
  246. return [[NSError alloc]
  247. initWithDomain:kFErrorDomain
  248. code:code
  249. userInfo:@{NSLocalizedDescriptionKey : desc}];
  250. }
  251. }
  252. + (NSNumber *)intForString:(NSString *)string {
  253. static dispatch_once_t once;
  254. static NSCharacterSet *notDigits;
  255. dispatch_once(&once, ^{
  256. notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
  257. });
  258. if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
  259. NSInteger num;
  260. NSScanner *scanner = [NSScanner scannerWithString:string];
  261. if ([scanner scanInteger:&num]) {
  262. return [NSNumber numberWithInteger:num];
  263. }
  264. }
  265. return nil;
  266. }
  267. + (NSInteger)int32min {
  268. return INTEGER_32_MIN;
  269. }
  270. + (NSInteger)int32max {
  271. return INTEGER_32_MAX;
  272. }
  273. + (NSString *)ieee754StringForNumber:(NSNumber *)val {
  274. double d = [val doubleValue];
  275. NSData *data = [NSData dataWithBytes:&d length:sizeof(double)];
  276. NSMutableString *str = [[NSMutableString alloc] init];
  277. const unsigned char *buffer = (const unsigned char *)[data bytes];
  278. for (int i = 0; i < data.length; i++) {
  279. unsigned char byte = buffer[7 - i];
  280. [str appendFormat:@"%02x", byte];
  281. }
  282. return str;
  283. }
  284. + (BOOL)tryParseString:(NSString *)string asInt:(NSInteger *)integer {
  285. return tryParseStringToInt(string, integer);
  286. }
  287. static inline BOOL tryParseStringToInt(__unsafe_unretained NSString *str,
  288. NSInteger *integer) {
  289. // First do some cheap checks (NOTE: The below checks are significantly
  290. // faster than an equivalent regex :-( ).
  291. NSUInteger length = str.length;
  292. if (length > 11 || length == 0) {
  293. return NO;
  294. }
  295. long long value = 0;
  296. BOOL negative = NO;
  297. NSUInteger i = 0;
  298. if ([str characterAtIndex:0] == '-') {
  299. if (length == 1) {
  300. return NO;
  301. }
  302. negative = YES;
  303. i = 1;
  304. }
  305. for (; i < length; i++) {
  306. unichar c = [str characterAtIndex:i];
  307. // Must be a digit, or '-' if it's the first char.
  308. if (c < '0' || c > '9') {
  309. return NO;
  310. } else {
  311. int charValue = c - '0';
  312. value = value * 10 + charValue;
  313. }
  314. }
  315. value = (negative) ? -value : value;
  316. if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
  317. return NO;
  318. } else {
  319. *integer = (NSInteger)value;
  320. return YES;
  321. }
  322. }
  323. + (NSString *)maxName {
  324. static dispatch_once_t once;
  325. static NSString *maxName;
  326. dispatch_once(&once, ^{
  327. maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
  328. });
  329. return maxName;
  330. }
  331. + (NSString *)minName {
  332. static dispatch_once_t once;
  333. static NSString *minName;
  334. dispatch_once(&once, ^{
  335. minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
  336. });
  337. return minName;
  338. }
  339. + (NSComparisonResult)compareKey:(NSString *)a toKey:(NSString *)b {
  340. if (a == b) {
  341. return NSOrderedSame;
  342. } else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
  343. return NSOrderedAscending;
  344. } else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
  345. return NSOrderedDescending;
  346. } else {
  347. NSInteger aAsInt, bAsInt;
  348. if (tryParseStringToInt(a, &aAsInt)) {
  349. if (tryParseStringToInt(b, &bAsInt)) {
  350. if (aAsInt > bAsInt) {
  351. return NSOrderedDescending;
  352. } else if (aAsInt < bAsInt) {
  353. return NSOrderedAscending;
  354. } else if (a.length > b.length) {
  355. return NSOrderedDescending;
  356. } else if (a.length < b.length) {
  357. return NSOrderedAscending;
  358. } else {
  359. return NSOrderedSame;
  360. }
  361. } else {
  362. return (NSComparisonResult)NSOrderedAscending;
  363. }
  364. } else if (tryParseStringToInt(b, &bAsInt)) {
  365. return (NSComparisonResult)NSOrderedDescending;
  366. } else {
  367. // Perform literal character by character search to prevent a > b &&
  368. // b > a issues. Note that calling -(NSString
  369. // *)decomposedStringWithCanonicalMapping also works.
  370. return [a compare:b options:NSLiteralSearch];
  371. }
  372. }
  373. }
  374. + (NSComparator)keyComparator {
  375. return ^NSComparisonResult(__unsafe_unretained NSString *a,
  376. __unsafe_unretained NSString *b) {
  377. return [FUtilities compareKey:a toKey:b];
  378. };
  379. }
  380. + (NSComparator)stringComparator {
  381. return ^NSComparisonResult(__unsafe_unretained NSString *a,
  382. __unsafe_unretained NSString *b) {
  383. return [a compare:b];
  384. };
  385. }
  386. + (double)randomDouble {
  387. return ((double)arc4random() / ARC4RANDOM_MAX);
  388. }
  389. @end