FBProtocolHelpers.m 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "FBProtocolHelpers.h"
  10. #import "FBErrorBuilder.h"
  11. #import "FBLogger.h"
  12. static NSString *const W3C_ELEMENT_KEY = @"element-6066-11e4-a52e-4f735466cecf";
  13. static NSString *const JSONWP_ELEMENT_KEY = @"ELEMENT";
  14. static NSString *const APPIUM_PREFIX = @"appium";
  15. static NSString *const ALWAYS_MATCH_KEY = @"alwaysMatch";
  16. static NSString *const FIRST_MATCH_KEY = @"firstMatch";
  17. NSDictionary<NSString *, id> *FBToElementDict(id element)
  18. {
  19. return @{
  20. W3C_ELEMENT_KEY: element,
  21. JSONWP_ELEMENT_KEY: element
  22. };
  23. }
  24. id FBExtractElement(NSDictionary *src)
  25. {
  26. for (NSString* key in src) {
  27. if ([key.lowercaseString isEqualToString:W3C_ELEMENT_KEY.lowercaseString]
  28. || [key.lowercaseString isEqualToString:JSONWP_ELEMENT_KEY.lowercaseString]) {
  29. return src[key];
  30. }
  31. }
  32. return nil;
  33. }
  34. NSDictionary *FBCleanupElements(NSDictionary *src)
  35. {
  36. NSMutableDictionary *result = src.mutableCopy;
  37. for (NSString* key in src) {
  38. if ([key.lowercaseString isEqualToString:W3C_ELEMENT_KEY.lowercaseString]
  39. || [key.lowercaseString isEqualToString:JSONWP_ELEMENT_KEY.lowercaseString]) {
  40. [result removeObjectForKey:key];
  41. }
  42. }
  43. return result.copy;
  44. }
  45. NSArray<NSString *> *standardCapabilities(void)
  46. {
  47. static NSArray<NSString *> *standardCaps;
  48. static dispatch_once_t onceStandardCaps;
  49. dispatch_once(&onceStandardCaps, ^{
  50. standardCaps = @[
  51. @"browserName",
  52. @"browserVersion",
  53. @"platformName",
  54. @"acceptInsecureCerts",
  55. @"pageLoadStrategy",
  56. @"proxy",
  57. @"setWindowRect",
  58. @"timeouts",
  59. @"unhandledPromptBehavior"
  60. ];
  61. });
  62. return standardCaps;
  63. }
  64. BOOL isStandardCap(NSString *capName)
  65. {
  66. return [standardCapabilities() containsObject:capName];
  67. }
  68. NSDictionary<NSString *, id> *_Nullable mergeCaps(NSDictionary<NSString *, id> *primary, NSDictionary<NSString *, id> *secondary, NSError **error)
  69. {
  70. NSMutableDictionary<NSString *, id> *result = primary.mutableCopy;
  71. for (NSString *capName in secondary) {
  72. if (nil != result[capName]) {
  73. [[[FBErrorBuilder builder]
  74. withDescriptionFormat:@"Property '%@' should not exist on both primary (%@) and secondary (%@) objects", capName, primary, secondary]
  75. buildError:error];
  76. return nil;
  77. }
  78. [result setObject:(id) secondary[capName] forKey:capName];
  79. }
  80. return result.copy;
  81. }
  82. NSDictionary<NSString *, id> *_Nullable stripPrefixes(NSDictionary<NSString *, id> *caps, NSError **error)
  83. {
  84. NSString* prefix = [NSString stringWithFormat:@"%@:", APPIUM_PREFIX];
  85. NSMutableDictionary<NSString *, id> *filteredCaps = [NSMutableDictionary dictionary];
  86. NSMutableArray<NSString *> *badPrefixedCaps = [NSMutableArray array];
  87. for (NSString *capName in caps) {
  88. if (![capName hasPrefix:prefix]) {
  89. [filteredCaps setObject:(id) caps[capName] forKey:capName];
  90. continue;
  91. }
  92. NSString *strippedName = [capName substringFromIndex:prefix.length];
  93. [filteredCaps setObject:(id) caps[capName] forKey:strippedName];
  94. if (isStandardCap(strippedName)) {
  95. [badPrefixedCaps addObject:strippedName];
  96. }
  97. }
  98. if (badPrefixedCaps.count > 0) {
  99. [[[FBErrorBuilder builder]
  100. withDescriptionFormat:@"The capabilities %@ are standard and should not have the '%@' prefix", badPrefixedCaps, prefix]
  101. buildError:error];
  102. return nil;
  103. }
  104. return filteredCaps.copy;
  105. }
  106. NSDictionary<NSString *, id> *FBParseCapabilities(NSDictionary<NSString *, id> *caps, NSError **error)
  107. {
  108. NSDictionary<NSString *, id> *alwaysMatch = caps[ALWAYS_MATCH_KEY] ?: @{};
  109. NSArray<NSDictionary<NSString *, id> *> *firstMatch = caps[FIRST_MATCH_KEY] ?: @[];
  110. NSArray<NSDictionary<NSString *, id> *> *allFirstMatchCaps = firstMatch.count == 0 ? @[@{}] : firstMatch;
  111. NSDictionary<NSString *, id> *requiredCaps;
  112. if (nil == (requiredCaps = stripPrefixes(alwaysMatch, error))) {
  113. return nil;
  114. }
  115. for (NSDictionary<NSString *, id> *fmc in allFirstMatchCaps) {
  116. NSDictionary<NSString *, id> *strippedCaps;
  117. if (nil == (strippedCaps = stripPrefixes(fmc, error))) {
  118. return nil;
  119. }
  120. NSDictionary<NSString *, id> *mergedCaps;
  121. if (nil == (mergedCaps = mergeCaps(requiredCaps, strippedCaps, error))) {
  122. [FBLogger logFmt:@"%@", (*error).description];
  123. continue;
  124. }
  125. return mergedCaps;
  126. }
  127. [[[FBErrorBuilder builder]
  128. withDescriptionFormat:@"Could not find matching capabilities from %@", caps]
  129. buildError:error];
  130. return nil;
  131. }