2017-05-15 12:27:07 -07:00
/*
* 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.
*/
2017-12-04 08:52:11 -08:00
#import <FirebaseCore/FIRLogger.h>
2017-05-15 12:27:07 -07:00
#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 () {
2017-06-30 10:47:37 -07:00
2017-05-15 12:27:07 -07:00
}
@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 ];
}
2017-06-30 10:47:37 -07:00
2017-05-15 12:27:07 -07:00
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 ;
2017-06-30 10:47:37 -07:00
int rangeLength = size ;
2017-05-15 12:27:07 -07:00
[ 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 ]);
2017-06-30 10:47:37 -07:00
2017-05-15 12:27:07 -07:00
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