Skip to content

Commit 514b780

Browse files
committed
review-comment: Fix form-urlencoded percent-decoding fallback
#7585 (comment)
1 parent 1f0d3f3 commit 514b780

2 files changed

Lines changed: 23 additions & 7 deletions

File tree

Sources/Swift/Integrations/SessionReplay/SentryReplayNetworkDetails.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,8 @@ enum NetworkBodyWarning: String {
161161
warnings.append(.bodyParseError)
162162
return parseText(data, encoding: encoding, warnings: &warnings)
163163
}
164-
// In form-urlencoded, + means space (rdar://40751862).
165-
let key = comps[0]
166-
.replacingOccurrences(of: "+", with: " ")
167-
.removingPercentEncoding ?? comps[0]
168-
let value = comps.dropFirst().joined(separator: "=")
169-
.replacingOccurrences(of: "+", with: " ")
170-
.removingPercentEncoding ?? comps[1]
164+
let key = decodeFormComponent(comps[0])
165+
let value = decodeFormComponent(comps.dropFirst().joined(separator: "="))
171166
guard !key.isEmpty else { continue }
172167
if let existing = formData[key] {
173168
if var list = existing as? [String] {
@@ -183,6 +178,13 @@ enum NetworkBodyWarning: String {
183178
return Body(content: formData, warnings: warnings)
184179
}
185180

181+
/// Decodes a form-urlencoded component: converts `+` to space and removes percent-encoding.
182+
/// Falls back to the `+`-to-space result if percent-decoding fails (e.g. `%ZZ`).
183+
private static func decodeFormComponent(_ component: String) -> String {
184+
let plusDecoded = component.replacingOccurrences(of: "+", with: " ")
185+
return plusDecoded.removingPercentEncoding ?? plusDecoded
186+
}
187+
186188
private static func parseText(_ data: Data, encoding: String.Encoding = .utf8, warnings: inout [NetworkBodyWarning]) -> Body {
187189
// Truncation at a multi-byte boundary (e.g. UTF-8 CJK, emoji) makes
188190
// String(data:encoding:) return nil. Try dropping up to 3 trailing bytes

Tests/SentryTests/Networking/SentryReplayNetworkDetailsBodyTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,20 @@ class SentryReplayNetworkDetailsBodyTests: XCTestCase {
295295
XCTAssertEqual(dict["name"] as? String, "Jane Doe")
296296
}
297297

298+
func testFormEncoded_malformedPercentEncoding_shouldPreservePlusDecodingAndEquals() throws {
299+
// %ZZ is invalid percent-encoding → removingPercentEncoding returns nil.
300+
// The fallback should still preserve +-to-space and = in values.
301+
let body = try XCTUnwrap(Body(
302+
data: "key=a%ZZ=b&greeting=hello+world".data(using: .utf8)!,
303+
contentType: "application/x-www-form-urlencoded; charset=utf-8"
304+
))
305+
306+
let dict = try XCTUnwrap(body.serialize()["body"] as? [String: Any])
307+
// Value preserves the joined "=" and the +-to-space conversion on the valid pair
308+
XCTAssertEqual(dict["key"] as? String, "a%ZZ=b")
309+
XCTAssertEqual(dict["greeting"] as? String, "hello world")
310+
}
311+
298312
// MARK: - Multi-byte Truncation
299313

300314
func testInit_withTruncatedMultiByteUTF8_shouldRecoverValidPrefix() throws {

0 commit comments

Comments
 (0)