| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- /*
- * 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 "FirebaseDatabase/Sources/Utilities/FValidation.h"
- #import "FirebaseDatabase/Sources/Constants/FConstants.h"
- #import "FirebaseDatabase/Sources/Utilities/FParsedUrl.h"
- #import "FirebaseDatabase/Sources/Utilities/FTypedefs.h"
- // Have to escape: * ? + [ ( ) { } ^ $ | \ . /
- // See:
- // https://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSRegularExpression_Class/Reference/Reference.html
- NSString *const kInvalidPathCharacters = @"[].#$";
- NSString *const kInvalidKeyCharacters = @"[].#$/";
- @implementation FValidation
- + (void)validateFrom:(NSString *)fn writablePath:(FPath *)path {
- if ([[path getFront] isEqualToString:kDotInfoPrefix]) {
- @throw [[NSException alloc]
- initWithName:@"WritablePathValidation"
- reason:[NSString
- stringWithFormat:@"(%@) failed to path %@: Can't "
- @"modify data under %@",
- fn, [path description],
- kDotInfoPrefix]
- userInfo:nil];
- }
- }
- + (void)validateFrom:(NSString *)fn knownEventType:(FIRDataEventType)event {
- switch (event) {
- case FIRDataEventTypeValue:
- case FIRDataEventTypeChildAdded:
- case FIRDataEventTypeChildChanged:
- case FIRDataEventTypeChildMoved:
- case FIRDataEventTypeChildRemoved:
- return;
- break;
- default:
- @throw [[NSException alloc]
- initWithName:@"KnownEventTypeValidation"
- reason:[NSString
- stringWithFormat:@"(%@) Unknown event type: %d",
- fn, (int)event]
- userInfo:nil];
- break;
- }
- }
- + (BOOL)isValidPathString:(NSString *)pathString {
- static dispatch_once_t token;
- static NSCharacterSet *badPathChars = nil;
- dispatch_once(&token, ^{
- badPathChars = [NSCharacterSet
- characterSetWithCharactersInString:kInvalidPathCharacters];
- });
- return pathString != nil && [pathString length] != 0 &&
- [pathString rangeOfCharacterFromSet:badPathChars].location ==
- NSNotFound;
- }
- + (void)validateFrom:(NSString *)fn validPathString:(NSString *)pathString {
- if (![self isValidPathString:pathString]) {
- @throw [[NSException alloc]
- initWithName:@"InvalidPathValidation"
- reason:[NSString stringWithFormat:
- @"(%@) Must be a non-empty string and "
- @"not contain '.' '#' '$' '[' or ']'",
- fn]
- userInfo:nil];
- }
- }
- + (void)validateFrom:(NSString *)fn validRootPathString:(NSString *)pathString {
- static dispatch_once_t token;
- static NSRegularExpression *dotInfoRegex = nil;
- dispatch_once(&token, ^{
- dotInfoRegex = [NSRegularExpression
- regularExpressionWithPattern:@"^\\/*\\.info(\\/|$)"
- options:0
- error:nil];
- });
- NSString *tempPath = pathString;
- // HACK: Obj-C regex are kinda' slow. Do a plain string search first before
- // bothering with the regex.
- if ([pathString rangeOfString:@".info"].location != NSNotFound) {
- tempPath = [dotInfoRegex
- stringByReplacingMatchesInString:pathString
- options:0
- range:NSMakeRange(0, pathString.length)
- withTemplate:@"/"];
- }
- [self validateFrom:fn validPathString:tempPath];
- }
- + (BOOL)isValidKey:(NSString *)key {
- static dispatch_once_t token;
- static NSCharacterSet *badKeyChars = nil;
- dispatch_once(&token, ^{
- badKeyChars = [NSCharacterSet
- characterSetWithCharactersInString:kInvalidKeyCharacters];
- });
- return key != nil && key.length > 0 &&
- [key rangeOfCharacterFromSet:badKeyChars].location == NSNotFound;
- }
- + (void)validateFrom:(NSString *)fn validKey:(NSString *)key {
- if (![self isValidKey:key]) {
- @throw [[NSException alloc]
- initWithName:@"InvalidKeyValidation"
- reason:[NSString
- stringWithFormat:
- @"(%@) Must be a non-empty string and not "
- @"contain '/' '.' '#' '$' '[' or ']'",
- fn]
- userInfo:nil];
- }
- }
- + (void)validateFrom:(NSString *)fn validURL:(FParsedUrl *)parsedUrl {
- NSString *pathString = [parsedUrl.path description];
- [self validateFrom:fn validRootPathString:pathString];
- }
- #pragma mark -
- #pragma mark Authentication validation
- + (BOOL)stringNonempty:(NSString *)str {
- return str != nil && ![str isKindOfClass:[NSNull class]] && str.length > 0;
- }
- + (void)validateToken:(NSString *)token {
- if (![FValidation stringNonempty:token]) {
- [NSException raise:NSInvalidArgumentException
- format:@"Can't have empty string or nil for custom token"];
- }
- }
- #pragma mark -
- #pragma mark Handling authentication errors
- /**
- * This function immediately calls the callback.
- * It assumes that it is not on FirebaseWorker thread.
- * It assumes it's on a user-controlled thread.
- */
- + (void)handleError:(NSError *)error
- withUserCallback:(fbt_void_nserror_id)userCallback {
- if (userCallback) {
- userCallback(error, nil);
- }
- }
- /**
- * This function immediately calls the callback.
- * It assumes that it is not on FirebaseWorker thread.
- * It assumes it's on a user-controlled thread.
- */
- + (void)handleError:(NSError *)error
- withSuccessCallback:(fbt_void_nserror)userCallback {
- if (userCallback) {
- userCallback(error);
- }
- }
- #pragma mark -
- #pragma mark Snapshot validation
- + (BOOL)validateFrom:(NSString *)fn
- isValidLeafValue:(id)value
- withPath:(NSArray *)path {
- if ([value isKindOfClass:[NSString class]]) {
- // Try to avoid conversion to bytes if possible
- NSString *theString = value;
- if ([theString maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding] >
- kFirebaseMaxLeafSize &&
- [theString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >
- kFirebaseMaxLeafSize) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:@"(%@) String exceeds max "
- @"size of %u utf8 bytes: %@",
- fn, (int)kFirebaseMaxLeafSize,
- pathString]
- userInfo:nil];
- }
- return YES;
- }
- else if ([value isKindOfClass:[NSNumber class]]) {
- // Cannot store NaN, but otherwise can store NSNumbers.
- if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:
- @"(%@) Cannot store NaN at path: %@.", fn,
- pathString]
- userInfo:nil];
- }
- return YES;
- }
- else if ([value isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dval = value;
- if (dval[kServerValueSubKey] != nil) {
- if ([dval count] > 1) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString = [[path subarrayWithRange:range]
- componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Cannot store other keys "
- @"with server value keys.%@.",
- fn, pathString]
- userInfo:nil];
- }
- return YES;
- }
- return NO;
- }
- else if (value == [NSNull null] || value == nil) {
- // Null is valid type to store at leaf
- return YES;
- }
- return NO;
- }
- + (NSString *)parseAndValidateKey:(id)keyId
- fromFunction:(NSString *)fn
- path:(NSArray *)path {
- if (![keyId isKindOfClass:[NSString class]]) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:@"(%@) Non-string keys are not "
- @"allowed in object at path: %@",
- fn, pathString]
- userInfo:nil];
- }
- return (NSString *)keyId;
- }
- + (void)validateFrom:(NSString *)fn
- validDictionaryKey:(id)keyId
- withPath:(NSArray *)path {
- NSString *key = [self parseAndValidateKey:keyId fromFunction:fn path:path];
- if (![key isEqualToString:kPayloadPriority] &&
- ![key isEqualToString:kPayloadValue] &&
- ![key isEqualToString:kServerValueSubKey] &&
- ![FValidation isValidKey:key]) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Invalid key in object at path: "
- @"%@. Keys must be non-empty and cannot "
- @"contain '/' '.' '#' '$' '[' or ']'",
- fn, pathString]
- userInfo:nil];
- }
- }
- + (void)validateFrom:(NSString *)fn
- validUpdateDictionaryKey:(id)keyId
- withValue:(id)value {
- FPath *path = [FPath pathWithString:[self parseAndValidateKey:keyId
- fromFunction:fn
- path:@[]]];
- __block NSInteger keyNum = 0;
- [path enumerateComponentsUsingBlock:^void(NSString *key, BOOL *stop) {
- if ([key isEqualToString:kPayloadPriority] &&
- keyNum == [path length] - 1) {
- [self validateFrom:fn isValidPriorityValue:value withPath:@[]];
- } else {
- keyNum++;
- if (![FValidation isValidKey:key]) {
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:
- @"(%@) Invalid key in object. Keys must "
- @"be non-empty and cannot contain '.' "
- @"'#' '$' '[' or ']'",
- fn]
- userInfo:nil];
- }
- }
- }];
- }
- + (void)validateFrom:(NSString *)fn
- isValidPriorityValue:(id)value
- withPath:(NSArray *)path {
- [self validateFrom:fn
- isValidPriorityValue:value
- withPath:path
- throwError:YES];
- }
- /**
- * Returns YES if priority is valid.
- */
- + (BOOL)validatePriorityValue:value {
- return [self validateFrom:nil
- isValidPriorityValue:value
- withPath:nil
- throwError:NO];
- }
- /**
- * Helper for validating priorities. If passed YES for throwError, it'll throw
- * descriptive errors on validation problems. Else, it'll just return YES/NO.
- */
- + (BOOL)validateFrom:(NSString *)fn
- isValidPriorityValue:(id)value
- withPath:(NSArray *)path
- throwError:(BOOL)throwError {
- if ([value isKindOfClass:[NSNumber class]]) {
- if ([[NSDecimalNumber notANumber] isEqualToNumber:value]) {
- if (throwError) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString = [[path subarrayWithRange:range]
- componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Cannot store NaN as "
- @"priority at path: %@.",
- fn, pathString]
- userInfo:nil];
- } else {
- return NO;
- }
- } else if (value == (id)kCFBooleanFalse ||
- value == (id)kCFBooleanTrue) {
- if (throwError) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString = [[path subarrayWithRange:range]
- componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Cannot store true/false "
- @"as priority at path: %@.",
- fn, pathString]
- userInfo:nil];
- } else {
- return NO;
- }
- }
- } else if ([value isKindOfClass:[NSDictionary class]]) {
- NSDictionary *dval = value;
- if (dval[kServerValueSubKey] != nil) {
- if ([dval count] > 1) {
- if (throwError) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString = [[path subarrayWithRange:range]
- componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:
- @"(%@) Cannot store other keys "
- @"with server value keys as "
- @"priority at path: %@.",
- fn, pathString]
- userInfo:nil];
- } else {
- return NO;
- }
- }
- } else {
- if (throwError) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString = [[path subarrayWithRange:range]
- componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString
- stringWithFormat:
- @"(%@) Cannot store an NSDictionary "
- @"as priority at path: %@.",
- fn, pathString]
- userInfo:nil];
- } else {
- return NO;
- }
- }
- } else if ([value isKindOfClass:[NSArray class]]) {
- if (throwError) {
- NSRange range;
- range.location = 0;
- range.length = MIN(path.count, 50);
- NSString *pathString =
- [[path subarrayWithRange:range] componentsJoinedByString:@"."];
- @throw [[NSException alloc]
- initWithName:@"InvalidFirebaseData"
- reason:[NSString stringWithFormat:
- @"(%@) Cannot store an NSArray as "
- @"priority at path: %@.",
- fn, pathString]
- userInfo:nil];
- } else {
- return NO;
- }
- }
- // It's valid!
- return YES;
- }
- @end
|