| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <FirebaseCore/FIRLogger.h>
- #import "FUtilities.h"
- #import "FStringUtilities.h"
- #import "FConstants.h"
- #import "FAtomicNumber.h"
- #define ARC4RANDOM_MAX 0x100000000
- #define INTEGER_32_MIN (-2147483648)
- #define INTEGER_32_MAX 2147483647
- #pragma mark -
- #pragma mark C functions
- static FLogLevel logLevel = FLogLevelInfo; // Default log level is info
- static NSMutableDictionary* options = nil;
- BOOL FFIsLoggingEnabled(FLogLevel level) {
- return level >= logLevel;
- }
- void firebaseJobsTroll(void) {
- FFLog(@"I-RDB095001", @"password super secret; JFK conspiracy; Hello there! Having fun digging through Firebase? We're always hiring! jobs@firebase.com");
- }
- #pragma mark -
- #pragma mark Private property and singleton specification
- @interface FUtilities() {
- }
- @property (nonatomic, strong) FAtomicNumber* localUid;
- + (FUtilities*)singleton;
- @end
- @implementation FUtilities
- @synthesize localUid;
- - (id)init
- {
- self = [super init];
- if (self) {
- self.localUid = [[FAtomicNumber alloc] init];
- }
- return self;
- }
- // TODO: We really want to be able to set the log level
- + (void) setLoggingEnabled:(BOOL)enabled {
- logLevel = enabled ? FLogLevelDebug : FLogLevelInfo;
- }
- + (BOOL) getLoggingEnabled {
- return logLevel == FLogLevelDebug;
- }
- + (FUtilities*) singleton
- {
- static dispatch_once_t pred = 0;
- __strong static id _sharedObject = nil;
- dispatch_once(&pred, ^{
- _sharedObject = [[self alloc] init]; // or some other init method
- });
- return _sharedObject;
- }
- // Refactor as a category of NSString
- + (NSArray *) splitString:(NSString *) str intoMaxSize:(const unsigned int) size {
- if(str.length <= size) {
- return [NSArray arrayWithObject:str];
- }
- NSMutableArray* dataSegs = [[NSMutableArray alloc] init];
- for(int c = 0; c < str.length; c += size) {
- if (c + size > str.length) {
- int rangeStart = c;
- unsigned long rangeLength = size - ((c + size) - str.length);
- [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
- }
- else {
- int rangeStart = c;
- int rangeLength = size;
- [dataSegs addObject:[str substringWithRange:NSMakeRange(rangeStart, rangeLength)]];
- }
- }
- return dataSegs;
- }
- + (NSNumber *) LUIDGenerator {
- FUtilities* f = [FUtilities singleton];
- return [f.localUid getAndIncrement];
- }
- + (NSString *) decodePath:(NSString *)pathString {
- NSMutableArray* decodedPieces = [[NSMutableArray alloc] init];
- NSArray* pieces = [pathString componentsSeparatedByString:@"/"];
- for (NSString* piece in pieces) {
- if (piece.length > 0) {
- [decodedPieces addObject:[FStringUtilities urlDecoded:piece]];
- }
- }
- return [NSString stringWithFormat:@"/%@", [decodedPieces componentsJoinedByString:@"/"]];
- }
- + (FParsedUrl *) parseUrl:(NSString *)url {
- NSString* original = url;
- //NSURL* n = [[NSURL alloc] initWithString:url]
- NSString* host;
- NSString* namespace;
- bool secure;
- NSString* scheme = nil;
- FPath* path = nil;
- NSRange colonIndex = [url rangeOfString:@"//"];
- if (colonIndex.location != NSNotFound) {
- scheme = [url substringToIndex:colonIndex.location - 1];
- url = [url substringFromIndex:colonIndex.location + 2];
- }
- NSInteger slashIndex = [url rangeOfString:@"/"].location;
- if (slashIndex == NSNotFound) {
- slashIndex = url.length;
- }
- host = [[url substringToIndex:slashIndex] lowercaseString];
- if (slashIndex >= url.length) {
- url = @"";
- } else {
- url = [url substringFromIndex:slashIndex + 1];
- }
- NSArray *parts = [host componentsSeparatedByString:@"."];
- if([parts count] == 3) {
- NSInteger colonIndex = [[parts objectAtIndex:2] rangeOfString:@":"].location;
- if (colonIndex != NSNotFound) {
- // we have a port, use the provided scheme
- secure = [scheme isEqualToString:@"https"];
- } else {
- secure = YES;
- }
- namespace = [[parts objectAtIndex:0] lowercaseString];
- NSString* pathString = [self decodePath:[NSString stringWithFormat:@"/%@", url]];
- path = [[FPath alloc] initWith:pathString];
- }
- else {
- [NSException raise:@"No Firebase database specified." format:@"No Firebase database found for input: %@", url];
- }
- FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:secure withNamespace:namespace];
- FFLog(@"I-RDB095002", @"---> Parsed (%@) to: (%@,%@); ns=(%@); path=(%@)", original, [repoInfo description], [repoInfo connectionURL], repoInfo.namespace, [path description]);
- FParsedUrl* parsedUrl = [[FParsedUrl alloc] init];
- parsedUrl.repoInfo = repoInfo;
- parsedUrl.path = path;
- return parsedUrl;
- }
- /*
- case str: JString => priString + "string:" + str.s;
- case bool: JBool => priString + "boolean:" + bool.value;
- case double: JDouble => priString + "number:" + double.num;
- case int: JInt => priString + "number:" + int.num;
- case _ => {
- error("Leaf node has value '" + data.value + "' of invalid type '" + data.value.getClass.toString + "'");
- "";
- }
- */
- + (NSString *) getJavascriptType:(id)obj {
- if ([obj isKindOfClass:[NSDictionary class]]) {
- return kJavaScriptObject;
- } else if([obj isKindOfClass:[NSString class]]) {
- return kJavaScriptString;
- }
- else if ([obj isKindOfClass:[NSNumber class]]) {
- // We used to just compare to @encode(BOOL) as suggested at
- // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber, but on arm64, @encode(BOOL) returns "B"
- // instead of "c" even though objCType still returns 'c' (signed char). So check both.
- if(strcmp([obj objCType], @encode(BOOL)) == 0 ||
- strcmp([obj objCType], @encode(signed char)) == 0) {
- return kJavaScriptBoolean;
- }
- else {
- return kJavaScriptNumber;
- }
- }
- else {
- return kJavaScriptNull;
- }
- }
- + (NSError *) errorForStatus:(NSString *)status andReason:(NSString *)reason {
- static dispatch_once_t pred = 0;
- __strong static NSDictionary* errorMap = nil;
- __strong static NSDictionary* errorCodes = nil;
- dispatch_once(&pred, ^{
- errorMap = @{
- @"permission_denied": @"Permission Denied",
- @"unavailable": @"Service is unavailable",
- kFErrorWriteCanceled: @"Write cancelled by user"
- };
- errorCodes = @{
- @"permission_denied": @1,
- @"unavailable": @2,
- kFErrorWriteCanceled: @3
- };
- });
- if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
- return nil;
- } else {
- NSInteger code;
- NSString* desc = nil;
- if (reason) {
- desc = reason;
- } else if ([errorMap objectForKey:status] != nil) {
- desc = [errorMap objectForKey:status];
- } else {
- desc = status;
- }
- if ([errorCodes objectForKey:status] != nil) {
- NSNumber* num = [errorCodes objectForKey:status];
- code = [num integerValue];
- } else {
- // XXX what to do here?
- code = 9999;
- }
- return [[NSError alloc] initWithDomain:kFErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: desc}];
- }
- }
- + (NSNumber *) intForString:(NSString *)string {
- static NSCharacterSet *notDigits = nil;
- if (!notDigits) {
- notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
- }
- if ([string rangeOfCharacterFromSet:notDigits].length == 0) {
- NSInteger num;
- NSScanner* scanner = [NSScanner scannerWithString:string];
- if ([scanner scanInteger:&num]) {
- return [NSNumber numberWithInteger:num];
- }
- }
- return nil;
- }
- + (NSString *) ieee754StringForNumber:(NSNumber *)val {
- double d = [val doubleValue];
- NSData* data = [NSData dataWithBytes:&d length:sizeof(double)];
- NSMutableString* str = [[NSMutableString alloc] init];
- const unsigned char* buffer = (const unsigned char*)[data bytes];
- for (int i = 0; i < data.length; i++) {
- unsigned char byte = buffer[7 - i];
- [str appendFormat:@"%02x", byte];
- }
- return str;
- }
- static inline BOOL tryParseStringToInt(__unsafe_unretained NSString* str, NSInteger* integer) {
- // First do some cheap checks (NOTE: The below checks are significantly faster than an equivalent regex :-( ).
- NSUInteger length = str.length;
- if (length > 11 || length == 0) {
- return NO;
- }
- long long value = 0;
- BOOL negative = NO;
- NSUInteger i = 0;
- if ([str characterAtIndex:0] == '-') {
- if (length == 1) {
- return NO;
- }
- negative = YES;
- i = 1;
- }
- for(; i < length; i++) {
- unichar c = [str characterAtIndex:i];
- // Must be a digit, or '-' if it's the first char.
- if (c < '0' || c > '9') {
- return NO;
- } else {
- int charValue = c - '0';
- value = value*10 + charValue;
- }
- }
- value = (negative) ? -value : value;
- if (value < INTEGER_32_MIN || value > INTEGER_32_MAX) {
- return NO;
- } else {
- *integer = (NSInteger)value;
- return YES;
- }
- }
- + (NSString *) maxName {
- static dispatch_once_t once;
- static NSString *maxName;
- dispatch_once(&once, ^{
- maxName = [[NSString alloc] initWithFormat:@"[MAX_NAME]"];
- });
- return maxName;
- }
- + (NSString *) minName {
- static dispatch_once_t once;
- static NSString *minName;
- dispatch_once(&once, ^{
- minName = [[NSString alloc] initWithFormat:@"[MIN_NAME]"];
- });
- return minName;
- }
- + (NSComparisonResult) compareKey:(NSString *)a toKey:(NSString *)b {
- if (a == b) {
- return NSOrderedSame;
- } else if (a == [FUtilities minName] || b == [FUtilities maxName]) {
- return NSOrderedAscending;
- } else if (b == [FUtilities minName] || a == [FUtilities maxName]) {
- return NSOrderedDescending;
- } else {
- NSInteger aAsInt, bAsInt;
- if (tryParseStringToInt(a, &aAsInt)) {
- if (tryParseStringToInt(b, &bAsInt)) {
- if (aAsInt > bAsInt) {
- return NSOrderedDescending;
- } else if (aAsInt < bAsInt) {
- return NSOrderedAscending;
- } else if (a.length > b.length) {
- return NSOrderedDescending;
- } else if (a.length < b.length) {
- return NSOrderedAscending;
- } else {
- return NSOrderedSame;
- }
- } else {
- return (NSComparisonResult) NSOrderedAscending;
- }
- } else if (tryParseStringToInt(b, &bAsInt)) {
- return (NSComparisonResult) NSOrderedDescending;
- } else {
- // Perform literal character by character search to prevent a > b && b > a issues.
- // Note that calling -(NSString *)decomposedStringWithCanonicalMapping also works.
- return [a compare:b options:NSLiteralSearch];
- }
- }
- }
- + (NSComparator) keyComparator {
- return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
- return [FUtilities compareKey:a toKey:b];
- };
- }
- + (NSComparator) stringComparator {
- return ^NSComparisonResult(__unsafe_unretained NSString *a, __unsafe_unretained NSString *b) {
- return [a compare:b];
- };
- }
- + (double) randomDouble {
- return ((double) arc4random() / ARC4RANDOM_MAX);
- }
- @end
|