fix: sanitize non-UTF-8-encodable strings before JSON response serialization - #1236
Conversation
…ization FBResponseJSONPayload asserted before its UTF-8 fallback could run when NSJSONSerialization returned nil outright, and the fallback itself (fb_utf8SafeStringWithReplacement:) mishandled unpaired UTF-16 surrogates since canBeConvertedToEncoding:/dataUsingEncoding:allowLossyConversion: misreport for that case. Fixes appium/appium#22673. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses JSON response serialization failures caused by strings containing unpaired UTF-16 surrogate code units (non-UTF-8-encodable), ensuring FBResponseJSONPayload can apply a sanitization fallback even when NSJSONSerialization returns nil outright.
Changes:
- Update
FBResponseJSONPayloadto fall back to a UTF-8-safe dictionary when initial JSON serialization fails or produces non-UTF-8 data. - Rework
fb_utf8SafeStringWithReplacement:to manually validate surrogate code units, replacing unpaired surrogates with U+FFFD. - Add/extend unit tests covering unpaired-surrogate sanitization and end-to-end response dispatch serialization, and wire the new test into the Xcode project.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| WebDriverAgentTests/UnitTests/NSDictionaryFBUtf8SafeTests.m | Adds coverage for unpaired surrogate sanitization and JSON serializability of sanitized output. |
| WebDriverAgentTests/UnitTests/FBResponseJSONPayloadTests.m | New tests verifying FBResponseJSONPayload dispatch sanitizes invalid strings (regression coverage for appium/appium#22673). |
| WebDriverAgentLib/Routing/FBResponseJSONPayload.m | Triggers sanitization fallback when JSON serialization returns nil or yields non-UTF-8 data. |
| WebDriverAgentLib/Categories/NSDictionary+FBUtf8SafeDictionary.m | Implements manual surrogate validation/replacement to correctly handle unpaired surrogates. |
| WebDriverAgent.xcodeproj/project.pbxproj | Adds the new unit test file to the test target sources. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| NSUInteger length = self.length; | ||
| NSMutableString *result = [NSMutableString stringWithCapacity:length]; | ||
| NSString *replacementStr = [NSString stringWithCharacters:&replacement length:1]; |
There was a problem hiding this comment.
Fixed in d557b0a — the function is now single-pass and lazily allocates the mutable string/copy only when an unpaired surrogate is actually found, so surrogate-free strings (the common case) return self untouched.
| if (nil == jsonData || nil == [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]) { | ||
| [FBLogger log:@"The incoming data cannot be encoded to UTF-8 JSON. Applying lossy conversion as a workaround."]; | ||
| jsonData = [NSJSONSerialization dataWithJSONObject:[self.dictionary fb_utf8SafeDictionary] |
There was a problem hiding this comment.
Fixed in d557b0a — reworded to "JSON serialization failed or produced non-UTF-8 data. Applying lossy conversion as a workaround."
| NSError *error; | ||
| NSDictionary *parsed = [NSJSONSerialization JSONObjectWithData:response.responseData | ||
| options:0 | ||
| error:&error]; | ||
| XCTAssertNil(error); |
| NSError *error; | ||
| NSData *jsonData = [NSJSONSerialization dataWithJSONObject:safe | ||
| options:0 | ||
| error:&error]; | ||
| XCTAssertNotNil(jsonData, @"Sanitized dictionary must be serializable to JSON, error of %@", error); |
There was a problem hiding this comment.
Fixed in d557b0a — reworded to "JSON serialization of the sanitized dictionary unexpectedly failed: %@".
|
[P2] Invalid UTF-16 dictionary keys remain unsanitized
If a response dictionary key contains an unpaired UTF-16 surrogate, both the initial serialization and the fallback serialization in Please either sanitize |
…rings Addresses PR review feedback on #1236: fb_utf8SafeDictionary only sanitized values, leaving invalid keys to still crash serialization; fb_utf8SafeStringWithReplacement: now avoids allocating/copying for the common case of strings without surrogate code units; clarified the FBResponseJSONPayload log message and fixed uninitialized NSError locals in tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Good catch — fixed in d557b0a. Added regression coverage for exactly the case you described — a key built from |
## [16.9.2](v16.9.1...v16.9.2) (2026-08-28) ### Bug Fixes * cache the testmanagerd protocol version fallback on timeout ([#1228](#1228)) ([f3d8e0c](f3d8e0c)) * sanitize non-UTF-8-encodable strings before JSON response serialization ([#1236](#1236)) ([fa6a250](fa6a250)) ### Miscellaneous Chores * **deps:** bump @appium/strongbox from 1.1.3 to 2.0.0 ([#1237](#1237)) ([03db844](03db844))
|
🎉 This PR is included in version 16.9.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
FBResponseJSONPayload asserted before its UTF-8 fallback could run when NSJSONSerialization returned nil outright, and the fallback itself (fb_utf8SafeStringWithReplacement:) mishandled unpaired UTF-16 surrogates since canBeConvertedToEncoding:/dataUsingEncoding:allowLossyConversion: misreport for that case.
Fixes appium/appium#22673.