Skip to content

Commit 481f525

Browse files
sumeruchatclaude
andauthored
SDK-469 Fix CodeQL string length conflation in deep link check (#1071)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7eb259c commit 481f525

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

swift-sdk/Internal/DeepLinkManager.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ class DeepLinkManager: NSObject {
9393
guard let regex = try? NSRegularExpression(pattern: Const.deepLinkRegex, options: []) else {
9494
return false
9595
}
96-
97-
return regex.firstMatch(in: urlString, options: [], range: NSMakeRange(0, urlString.count)) != nil
96+
97+
// Build the range with NSRange(_:in:) so it is in the UTF-16 coordinates
98+
// NSRegularExpression expects. urlString.count conflates Swift String length
99+
// with NSString length for non-BMP characters (CWE-135, SDK-469).
100+
let range = NSRange(urlString.startIndex..<urlString.endIndex, in: urlString)
101+
return regex.firstMatch(in: urlString, options: [], range: range) != nil
98102
}
99103

100104
private lazy var redirectUrlSession: NetworkSessionProtocol = {

tests/unit-tests/DeepLinkTests.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,26 @@ class DeepLinkTests: XCTestCase {
149149
func testIsIterableDeepLinkReturnsFalseForEmptyString() {
150150
XCTAssertFalse(IterableAPI.isIterableDeepLink(""))
151151
}
152-
152+
153+
// SDK-469: when the URL contains non-BMP characters (e.g. emoji) BEFORE the
154+
// deep-link path segment, String.count under-counts vs NSString.length (each
155+
// 👍🏿 is one Character but four UTF-16 code units). A search range built from
156+
// String.count stops short of the trailing "/a/..." segment and misses the
157+
// match; a range built from NSString.length covers the full string.
158+
//
159+
// The emoji prefix is long enough that the count deficit pushes the entire
160+
// "/a/..." token past the truncated range, so this test FAILS against the old
161+
// String.count-based implementation and passes with the NSString.length fix.
162+
func testIsIterableDeepLinkMatchesWithNonBMPCharactersBeforePath() {
163+
let urlWithEmoji = "https://links.iterable.com/" + String(repeating: "👍🏿", count: 20) + "/a/60402396fbd5433eb35397b47ab2fb83"
164+
XCTAssertTrue(IterableAPI.isIterableDeepLink(urlWithEmoji))
165+
}
166+
167+
func testIsIterableDeepLinkHandlesNonBMPCharactersWithoutDeepLinkPath() {
168+
let urlWithEmoji = "https://example.com/" + String(repeating: "👍🏿", count: 20) + "/some/path"
169+
XCTAssertFalse(IterableAPI.isIterableDeepLink(urlWithEmoji))
170+
}
171+
153172
// MARK: - Other Tests
154173

155174
/// this is a service that automatically redirects if that url is hit, make sure we are not actually hitting the url but our servers

0 commit comments

Comments
 (0)