appium-webdriveragent 16.9.1 → 16.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [16.9.2](https://github.com/appium/WebDriverAgent/compare/v16.9.1...v16.9.2) (2026-08-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ * cache the testmanagerd protocol version fallback on timeout ([#1228](https://github.com/appium/WebDriverAgent/issues/1228)) ([f3d8e0c](https://github.com/appium/WebDriverAgent/commit/f3d8e0ce42488b6e95a74f1c59e0f20a500d1a54))
6
+ * sanitize non-UTF-8-encodable strings before JSON response serialization ([#1236](https://github.com/appium/WebDriverAgent/issues/1236)) ([fa6a250](https://github.com/appium/WebDriverAgent/commit/fa6a2503222ac2ffd039cb73a96689467774d7f2))
7
+
8
+ ### Miscellaneous Chores
9
+
10
+ * **deps:** bump @appium/strongbox from 1.1.3 to 2.0.0 ([#1237](https://github.com/appium/WebDriverAgent/issues/1237)) ([03db844](https://github.com/appium/WebDriverAgent/commit/03db844ee0cb408bb323d0d77241ba55188c47f3))
11
+
1
12
  ## [16.9.1](https://github.com/appium/WebDriverAgent/compare/v16.9.0...v16.9.1) (2026-08-28)
2
13
 
3
14
  ### Bug Fixes
@@ -604,6 +604,7 @@
604
604
  716F0DA12A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */; };
605
605
  716F0DA32A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 716F0DA02A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m */; };
606
606
  716F0DA62A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 716F0DA52A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m */; };
607
+ E82332D32C4E06280CBE3F4C /* FBResponseJSONPayloadTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F5C37472190A686297D3534 /* FBResponseJSONPayloadTests.m */; };
607
608
  7182A87F3CAA27F71B624AD2 /* XCTRunnerAutomationSession-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 01AF8E73DD47455B4854E470 /* XCTRunnerAutomationSession-Protocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
608
609
  718F49C8230844330045FE8B /* FBProtocolHelpersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 718F49C7230844330045FE8B /* FBProtocolHelpersTests.m */; };
609
610
  718F49C923087ACF0045FE8B /* FBProtocolHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B155DD23080CA600646AFB /* FBProtocolHelpers.h */; };
@@ -1595,6 +1596,7 @@
1595
1596
  716F0D9F2A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+FBUtf8SafeDictionary.h"; sourceTree = "<group>"; };
1596
1597
  716F0DA02A16CA1000CDD977 /* NSDictionary+FBUtf8SafeDictionary.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+FBUtf8SafeDictionary.m"; sourceTree = "<group>"; };
1597
1598
  716F0DA52A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSDictionaryFBUtf8SafeTests.m; sourceTree = "<group>"; };
1599
+ 3F5C37472190A686297D3534 /* FBResponseJSONPayloadTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBResponseJSONPayloadTests.m; sourceTree = "<group>"; };
1598
1600
  717C0D702518ED2800CAA6EC /* TVOSSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TVOSSettings.xcconfig; sourceTree = "<group>"; };
1599
1601
  717C0D862518ED7000CAA6EC /* TVOSTestSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TVOSTestSettings.xcconfig; sourceTree = "<group>"; };
1600
1602
  7183E8C2B556594311CB8898 /* XCUIRemoteSiriInterface-Protocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XCUIRemoteSiriInterface-Protocol.h"; sourceTree = "<group>"; };
@@ -2682,6 +2684,7 @@
2682
2684
  712A0C841DA3E459007D02E5 /* FBXPathTests.m */,
2683
2685
  EE9B76581CF7987300275851 /* Info.plist */,
2684
2686
  716F0DA52A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m */,
2687
+ 3F5C37472190A686297D3534 /* FBResponseJSONPayloadTests.m */,
2685
2688
  7139145B1DF01A12005896C2 /* NSExpressionFBFormatTests.m */,
2686
2689
  71A224E71DE326C500844D55 /* NSPredicateFBFormatTests.m */,
2687
2690
  713914591DF01989005896C2 /* XCUIElementHelpersTests.m */,
@@ -4811,6 +4814,7 @@
4811
4814
  71A224E81DE326C500844D55 /* NSPredicateFBFormatTests.m in Sources */,
4812
4815
  EE6A892B1D0B25820083E92B /* XCUIApplicationDouble.m in Sources */,
4813
4816
  716F0DA62A17323300CDD977 /* NSDictionaryFBUtf8SafeTests.m in Sources */,
4817
+ E82332D32C4E06280CBE3F4C /* FBResponseJSONPayloadTests.m in Sources */,
4814
4818
  EE6A892D1D0B2AF40083E92B /* FBErrorBuilderTests.m in Sources */,
4815
4819
  712A0C851DA3E459007D02E5 /* FBXPathTests.m in Sources */,
4816
4820
  ADBC39981D07842800327304 /* XCUIElementDouble.m in Sources */,
@@ -14,32 +14,42 @@ const unichar REPLACER = 0xfffd;
14
14
 
15
15
  - (instancetype)fb_utf8SafeStringWithReplacement:(unichar)replacement
16
16
  {
17
- if ([self canBeConvertedToEncoding:NSUTF8StringEncoding]) {
18
- return self;
19
- }
20
-
21
- NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
22
- NSString *convertedString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
23
- NSMutableString *result = [NSMutableString string];
24
- NSString *replacementStr = [NSString stringWithCharacters:&replacement length:1];
25
- NSUInteger originalIdx = 0;
26
- NSUInteger convertedIdx = 0;
27
- while (originalIdx < [self length] && convertedIdx < [convertedString length]) {
28
- unichar originalChar = [self characterAtIndex:originalIdx];
29
- unichar convertedChar = [convertedString characterAtIndex:convertedIdx];
30
-
31
- if (originalChar == convertedChar) {
32
- [result appendString:[NSString stringWithCharacters:&originalChar length:1]];
33
- originalIdx++;
34
- convertedIdx++;
17
+ // -canBeConvertedToEncoding: and -dataUsingEncoding:allowLossyConversion:
18
+ // both misreport strings containing unpaired UTF-16 surrogates, so the
19
+ // code units are validated manually instead of relying on them.
20
+ NSUInteger length = self.length;
21
+ NSMutableString *result = nil;
22
+ NSString *replacementStr = nil;
23
+ NSUInteger copiedIdx = 0;
24
+ NSUInteger idx = 0;
25
+ while (idx < length) {
26
+ unichar c = [self characterAtIndex:idx];
27
+ if (c >= 0xD800 && c <= 0xDBFF && idx + 1 < length) {
28
+ unichar next = [self characterAtIndex:idx + 1];
29
+ if (next >= 0xDC00 && next <= 0xDFFF) {
30
+ idx += 2;
31
+ continue;
32
+ }
33
+ }
34
+ if (c < 0xD800 || c > 0xDFFF) {
35
+ idx += 1;
35
36
  continue;
36
37
  }
37
-
38
- while (originalChar != convertedChar && originalIdx < [self length]) {
39
- [result appendString:replacementStr];
40
- originalChar = [self characterAtIndex:++originalIdx];
38
+ // Unpaired surrogate found. Lazily allocate the result and copy over
39
+ // the valid run preceding it, so strings without any are returned as-is.
40
+ if (nil == result) {
41
+ result = [NSMutableString stringWithCapacity:length];
42
+ replacementStr = [NSString stringWithCharacters:&replacement length:1];
41
43
  }
44
+ [result appendString:[self substringWithRange:NSMakeRange(copiedIdx, idx - copiedIdx)]];
45
+ [result appendString:replacementStr];
46
+ idx += 1;
47
+ copiedIdx = idx;
48
+ }
49
+ if (nil == result) {
50
+ return self;
42
51
  }
52
+ [result appendString:[self substringWithRange:NSMakeRange(copiedIdx, length - copiedIdx)]];
43
53
  return result.copy;
44
54
  }
45
55
 
@@ -70,16 +80,24 @@ const unichar REPLACER = 0xfffd;
70
80
 
71
81
  - (instancetype)fb_utf8SafeDictionary
72
82
  {
73
- NSMutableDictionary *result = [self mutableCopy];
83
+ NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:self.count];
74
84
  for (id key in self) {
75
- id value = result[key];
85
+ id value = self[key];
86
+ id safeValue = value;
76
87
  if ([value isKindOfClass:NSString.class]) {
77
- result[key] = [(NSString *)value fb_utf8SafeStringWithReplacement:REPLACER];
88
+ safeValue = [(NSString *)value fb_utf8SafeStringWithReplacement:REPLACER];
78
89
  } else if ([value isKindOfClass:NSArray.class]) {
79
- result[key] = [(NSArray *)value fb_utf8SafeArray];
90
+ safeValue = [(NSArray *)value fb_utf8SafeArray];
80
91
  } else if ([value isKindOfClass:NSDictionary.class]) {
81
- result[key] = [(NSDictionary *)value fb_utf8SafeDictionary];
92
+ safeValue = [(NSDictionary *)value fb_utf8SafeDictionary];
82
93
  }
94
+ // Sanitized keys could theoretically collide (e.g. two distinct invalid
95
+ // keys both reducing to the same replacement string); the later one
96
+ // wins, same as any other NSDictionary literal with duplicate keys.
97
+ id safeKey = [key isKindOfClass:NSString.class]
98
+ ? [(NSString *)key fb_utf8SafeStringWithReplacement:REPLACER]
99
+ : key;
100
+ result[safeKey] = safeValue;
83
101
  }
84
102
  return result.copy;
85
103
  }
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.9.1</string>
18
+ <string>16.9.2</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.9.1</string>
22
+ <string>16.9.2</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -43,9 +43,8 @@
43
43
  NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.dictionary
44
44
  options:NSJSONWritingPrettyPrinted
45
45
  error:&error];
46
- NSCAssert(jsonData, @"Valid JSON must be responded, error of %@", error);
47
- if (nil == [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]) {
48
- [FBLogger log:@"The incoming data cannot be encoded to UTF-8 JSON. Applying lossy conversion as a workaround."];
46
+ if (nil == jsonData || nil == [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]) {
47
+ [FBLogger log:@"JSON serialization failed or produced non-UTF-8 data. Applying lossy conversion as a workaround."];
49
48
  jsonData = [NSJSONSerialization dataWithJSONObject:[self.dictionary fb_utf8SafeDictionary]
50
49
  options:NSJSONWritingPrettyPrinted
51
50
  error:&error];
@@ -81,8 +81,9 @@
81
81
 
82
82
  NSInteger FBTestmanagerdVersion(void)
83
83
  {
84
- // Not dispatch_once: that would permanently cache the timeout fallback below if the first call's
85
- // reply merely arrived late. -1 means "not yet determined"; a timeout isn't cached, so it retries.
84
+ // -1 means "not yet determined". The timeout fallback is cached like any other outcome: the
85
+ // value is diagnostic-only, and retrying would stall every later /status for the full timeout
86
+ // against a daemon that never answers.
86
87
  static NSInteger cachedVersion = -1;
87
88
  static dispatch_queue_t syncQueue;
88
89
  static dispatch_once_t onceToken;
@@ -108,12 +109,11 @@ NSInteger FBTestmanagerdVersion(void)
108
109
  }];
109
110
  int64_t timeoutNs = (int64_t)(TESTMANAGERD_VERSION_TIMEOUT_SEC * NSEC_PER_SEC);
110
111
  if (0 != dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, timeoutNs))) {
111
- // Assume newest/full-featured on timeout, but don't cache it - retry on the next call.
112
112
  [FBLogger logFmt:@"Did not receive a testmanagerd protocol version reply within %d seconds; assuming the newest/full-featured protocol", TESTMANAGERD_VERSION_TIMEOUT_SEC];
113
113
  result = 0xFFFF;
114
- return;
114
+ } else {
115
+ result = receivedVersion;
115
116
  }
116
- result = receivedVersion;
117
117
  } else {
118
118
  // Modern testmanagerd (Xcode 15+) negotiates named XCTCapabilities instead of a scalar
119
119
  // version; there's no direct integer equivalent, so just confirm capabilities negotiated.
@@ -0,0 +1,76 @@
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.
7
+ */
8
+
9
+ #import <XCTest/XCTest.h>
10
+
11
+ #import "FBResponseJSONPayload.h"
12
+ #import "RouteResponse.h"
13
+
14
+ @interface FBResponseJSONPayloadTests : XCTestCase
15
+ @end
16
+
17
+ @implementation FBResponseJSONPayloadTests
18
+
19
+ // https://github.com/appium/appium/issues/22673
20
+ - (void)testDispatchSanitizesNonUtf8EncodableStrings
21
+ {
22
+ unichar chars[] = {'a', 'b', 'c', 0xD800, 'd', 'e', 'f'};
23
+ NSString *unsafe = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(unichar)];
24
+ NSDictionary *dictionary = @{@"value": unsafe};
25
+ FBResponseJSONPayload *payload = [[FBResponseJSONPayload alloc] initWithDictionary:dictionary
26
+ httpStatusCode:kHTTPStatusCodeOK];
27
+ RouteResponse *response = [RouteResponse new];
28
+
29
+ XCTAssertNoThrow([payload dispatchWithResponse:response]);
30
+ XCTAssertNotNil(response.responseData);
31
+
32
+ NSError *error = nil;
33
+ NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:response.responseData
34
+ options:0
35
+ error:&error];
36
+ XCTAssertNil(error);
37
+ XCTAssertEqualObjects(parsed[@"value"], @"abc�def");
38
+ }
39
+
40
+ // Dictionary keys must be sanitized too, not just values
41
+ - (void)testDispatchSanitizesNonUtf8EncodableKeys
42
+ {
43
+ unichar chars[] = {'k', 0xD800, 'y'};
44
+ NSString *unsafeKey = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(unichar)];
45
+ NSDictionary *dictionary = @{unsafeKey: @"value"};
46
+ FBResponseJSONPayload *payload = [[FBResponseJSONPayload alloc] initWithDictionary:dictionary
47
+ httpStatusCode:kHTTPStatusCodeOK];
48
+ RouteResponse *response = [RouteResponse new];
49
+
50
+ XCTAssertNoThrow([payload dispatchWithResponse:response]);
51
+ XCTAssertNotNil(response.responseData);
52
+
53
+ NSError *error = nil;
54
+ NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:response.responseData
55
+ options:0
56
+ error:&error];
57
+ XCTAssertNil(error);
58
+ XCTAssertEqualObjects(parsed[@"k�y"], @"value");
59
+ }
60
+
61
+ - (void)testDispatchWithRegularDictionary
62
+ {
63
+ NSDictionary *dictionary = @{@"value": @"regular string"};
64
+ FBResponseJSONPayload *payload = [[FBResponseJSONPayload alloc] initWithDictionary:dictionary
65
+ httpStatusCode:kHTTPStatusCodeOK];
66
+ RouteResponse *response = [RouteResponse new];
67
+
68
+ [payload dispatchWithResponse:response];
69
+
70
+ NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:response.responseData
71
+ options:0
72
+ error:nil];
73
+ XCTAssertEqualObjects(parsed, dictionary);
74
+ }
75
+
76
+ @end
@@ -31,4 +31,47 @@
31
31
  XCTAssertEqualObjects(d, d.fb_utf8SafeDictionary);
32
32
  }
33
33
 
34
+ - (void)testUnpairedSurrogateSanitization
35
+ {
36
+ unichar chars[] = {'a', 'b', 'c', 0xD800, 'd', 'e', 'f'};
37
+ NSString *unsafe = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(unichar)];
38
+ NSDictionary *d = @{
39
+ @"key": unsafe,
40
+ @"nested": @{@"value": @[unsafe]},
41
+ };
42
+ NSDictionary *safe = d.fb_utf8SafeDictionary;
43
+
44
+ NSString *expected = @"abc�def";
45
+ XCTAssertEqualObjects(safe[@"key"], expected);
46
+ XCTAssertEqualObjects(safe[@"nested"][@"value"][0], expected);
47
+
48
+ NSError *error = nil;
49
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:safe
50
+ options:0
51
+ error:&error];
52
+ XCTAssertNotNil(jsonData, @"JSON serialization of the sanitized dictionary unexpectedly failed: %@", error);
53
+ }
54
+
55
+ - (void)testUnpairedSurrogateKeySanitization
56
+ {
57
+ unichar chars[] = {'k', 0xD800, 'y'};
58
+ NSString *unsafeKey = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(unichar)];
59
+ NSDictionary *d = @{unsafeKey: @"value"};
60
+ NSDictionary *safe = d.fb_utf8SafeDictionary;
61
+
62
+ XCTAssertEqualObjects(safe[@"k�y"], @"value");
63
+
64
+ NSError *error = nil;
65
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:safe
66
+ options:0
67
+ error:&error];
68
+ XCTAssertNotNil(jsonData, @"JSON serialization of the sanitized dictionary unexpectedly failed: %@", error);
69
+ }
70
+
71
+ - (void)testValidSurrogatePairIsPreserved
72
+ {
73
+ NSString *emoji = @"a😀b";
74
+ XCTAssertEqualObjects([emoji fb_utf8SafeStringWithReplacement:0xfffd], emoji);
75
+ }
76
+
34
77
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.9.1",
3
+ "version": "16.9.2",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@appium/base-driver": "^10.3.0",
68
- "@appium/strongbox": "^1.0.0-rc.1",
68
+ "@appium/strongbox": "^2.0.0",
69
69
  "@appium/support": "^7.2.1",
70
70
  "appium-ios-simulator": "^9.0.0",
71
71
  "async-lock": "^1.0.0",