Skip to content

Commit 11cd552

Browse files
committed
Fix crash in formatMessageAsText: use NSString length for NSRanges and guard slicing before matches to avoid invalid ranges with multi-byte content
1 parent cf1bfda commit 11cd552

File tree

1 file changed

+20
-14
lines changed

1 file changed

+20
-14
lines changed

bitchat/ViewModels/ChatViewModel.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3161,9 +3161,12 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
31613161

31623162
// For extremely long content, render as plain text to avoid heavy regex/layout work,
31633163
// unless the content includes Cashu tokens we want to chip-render below
3164+
// Compute NSString-backed length for regex/nsrange correctness with multi-byte characters
3165+
let nsContent = content as NSString
3166+
let nsLen = nsContent.length
31643167
let containsCashuEarly: Bool = {
31653168
let rx = Regexes.quickCashuPresence
3166-
return rx.numberOfMatches(in: content, options: [], range: NSRange(location: 0, length: content.count)) > 0
3169+
return rx.numberOfMatches(in: content, options: [], range: NSRange(location: 0, length: nsLen)) > 0
31673170
}()
31683171
if (content.count > 4000 || content.hasVeryLongToken(threshold: 1024)) && !containsCashuEarly {
31693172
var plainStyle = AttributeContainer()
@@ -3181,8 +3184,6 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
31813184
let lnurlRegex = Regexes.lnurl
31823185
let lightningSchemeRegex = Regexes.lightningScheme
31833186
let detector = Regexes.linkDetector
3184-
3185-
let nsLen = content.count
31863187
let hasMentionsHint = content.contains("@")
31873188
let hasHashtagsHint = content.contains("#")
31883189
let hasURLHint = content.contains("://") || content.contains("www.") || content.contains("http")
@@ -3262,17 +3263,19 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
32623263
for (range, type) in allMatches {
32633264
// Add text before match
32643265
if let nsRange = Range(range, in: content) {
3265-
let beforeText = String(content[lastEnd..<nsRange.lowerBound])
3266-
if !beforeText.isEmpty {
3267-
var beforeStyle = AttributeContainer()
3268-
beforeStyle.foregroundColor = baseColor
3269-
beforeStyle.font = isSelf
3270-
? .system(size: 14, weight: .bold, design: .monospaced)
3271-
: .system(size: 14, design: .monospaced)
3272-
if isMentioned {
3273-
beforeStyle.font = beforeStyle.font?.bold()
3266+
if lastEnd < nsRange.lowerBound {
3267+
let beforeText = String(content[lastEnd..<nsRange.lowerBound])
3268+
if !beforeText.isEmpty {
3269+
var beforeStyle = AttributeContainer()
3270+
beforeStyle.foregroundColor = baseColor
3271+
beforeStyle.font = isSelf
3272+
? .system(size: 14, weight: .bold, design: .monospaced)
3273+
: .system(size: 14, design: .monospaced)
3274+
if isMentioned {
3275+
beforeStyle.font = beforeStyle.font?.bold()
3276+
}
3277+
result.append(AttributedString(beforeText).mergingAttributes(beforeStyle))
32743278
}
3275-
result.append(AttributedString(beforeText).mergingAttributes(beforeStyle))
32763279
}
32773280

32783281
// Add styled match
@@ -3380,7 +3383,10 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
33803383
result.append(AttributedString(matchText).mergingAttributes(matchStyle))
33813384
}
33823385
}
3383-
lastEnd = nsRange.upperBound
3386+
// Advance lastEnd safely in case of overlaps
3387+
if lastEnd < nsRange.upperBound {
3388+
lastEnd = nsRange.upperBound
3389+
}
33843390
}
33853391
}
33863392

0 commit comments

Comments
 (0)