/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #import "FBProtocolHelpers.h" #import "FBErrorBuilder.h" #import "FBLogger.h" static NSString *const W3C_ELEMENT_KEY = @"element-6066-11e4-a52e-4f735466cecf"; static NSString *const JSONWP_ELEMENT_KEY = @"ELEMENT"; static NSString *const APPIUM_PREFIX = @"appium"; static NSString *const ALWAYS_MATCH_KEY = @"alwaysMatch"; static NSString *const FIRST_MATCH_KEY = @"firstMatch"; NSDictionary *FBToElementDict(id element) { return @{ W3C_ELEMENT_KEY: element, JSONWP_ELEMENT_KEY: element }; } id FBExtractElement(NSDictionary *src) { for (NSString* key in src) { if ([key.lowercaseString isEqualToString:W3C_ELEMENT_KEY.lowercaseString] || [key.lowercaseString isEqualToString:JSONWP_ELEMENT_KEY.lowercaseString]) { return src[key]; } } return nil; } NSDictionary *FBCleanupElements(NSDictionary *src) { NSMutableDictionary *result = src.mutableCopy; for (NSString* key in src) { if ([key.lowercaseString isEqualToString:W3C_ELEMENT_KEY.lowercaseString] || [key.lowercaseString isEqualToString:JSONWP_ELEMENT_KEY.lowercaseString]) { [result removeObjectForKey:key]; } } return result.copy; } NSArray *standardCapabilities(void) { static NSArray *standardCaps; static dispatch_once_t onceStandardCaps; dispatch_once(&onceStandardCaps, ^{ standardCaps = @[ @"browserName", @"browserVersion", @"platformName", @"acceptInsecureCerts", @"pageLoadStrategy", @"proxy", @"setWindowRect", @"timeouts", @"unhandledPromptBehavior" ]; }); return standardCaps; } BOOL isStandardCap(NSString *capName) { return [standardCapabilities() containsObject:capName]; } NSDictionary *_Nullable mergeCaps(NSDictionary *primary, NSDictionary *secondary, NSError **error) { NSMutableDictionary *result = primary.mutableCopy; for (NSString *capName in secondary) { if (nil != result[capName]) { [[[FBErrorBuilder builder] withDescriptionFormat:@"Property '%@' should not exist on both primary (%@) and secondary (%@) objects", capName, primary, secondary] buildError:error]; return nil; } [result setObject:(id) secondary[capName] forKey:capName]; } return result.copy; } NSDictionary *_Nullable stripPrefixes(NSDictionary *caps, NSError **error) { NSString* prefix = [NSString stringWithFormat:@"%@:", APPIUM_PREFIX]; NSMutableDictionary *filteredCaps = [NSMutableDictionary dictionary]; NSMutableArray *badPrefixedCaps = [NSMutableArray array]; for (NSString *capName in caps) { if (![capName hasPrefix:prefix]) { [filteredCaps setObject:(id) caps[capName] forKey:capName]; continue; } NSString *strippedName = [capName substringFromIndex:prefix.length]; [filteredCaps setObject:(id) caps[capName] forKey:strippedName]; if (isStandardCap(strippedName)) { [badPrefixedCaps addObject:strippedName]; } } if (badPrefixedCaps.count > 0) { [[[FBErrorBuilder builder] withDescriptionFormat:@"The capabilities %@ are standard and should not have the '%@' prefix", badPrefixedCaps, prefix] buildError:error]; return nil; } return filteredCaps.copy; } NSDictionary *FBParseCapabilities(NSDictionary *caps, NSError **error) { NSDictionary *alwaysMatch = caps[ALWAYS_MATCH_KEY] ?: @{}; NSArray *> *firstMatch = caps[FIRST_MATCH_KEY] ?: @[]; NSArray *> *allFirstMatchCaps = firstMatch.count == 0 ? @[@{}] : firstMatch; NSDictionary *requiredCaps; if (nil == (requiredCaps = stripPrefixes(alwaysMatch, error))) { return nil; } for (NSDictionary *fmc in allFirstMatchCaps) { NSDictionary *strippedCaps; if (nil == (strippedCaps = stripPrefixes(fmc, error))) { return nil; } NSDictionary *mergedCaps; if (nil == (mergedCaps = mergeCaps(requiredCaps, strippedCaps, error))) { [FBLogger logFmt:@"%@", (*error).description]; continue; } return mergedCaps; } [[[FBErrorBuilder builder] withDescriptionFormat:@"Could not find matching capabilities from %@", caps] buildError:error]; return nil; }