Skip to content

Commit 3e5043f

Browse files
committed
Harden text formatting against Unicode range mismatches
- Use NSString.length for all NSRange regex scans - Guard string slicing when lastEnd > match.lowerBound - Advance lastEnd safely to avoid backtracking overlaps - Apply to formatMessageContent, formatMessage, formatMessageAsText, parseMentions
1 parent 11cd552 commit 3e5043f

File tree

1 file changed

+28
-18
lines changed

1 file changed

+28
-18
lines changed

bitchat/ViewModels/ChatViewModel.swift

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,8 +3043,10 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
30433043
let mentionRegex = try? NSRegularExpression(pattern: mentionPattern, options: [])
30443044
let hashtagRegex = try? NSRegularExpression(pattern: hashtagPattern, options: [])
30453045

3046-
let mentionMatches = mentionRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: contentText.count)) ?? []
3047-
let hashtagMatches = hashtagRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: contentText.count)) ?? []
3046+
let nsContent = contentText as NSString
3047+
let nsLen = nsContent.length
3048+
let mentionMatches = mentionRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: nsLen)) ?? []
3049+
let hashtagMatches = hashtagRegex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: nsLen)) ?? []
30483050

30493051
// Combine and sort all matches
30503052
var allMatches: [(range: NSRange, type: String)] = []
@@ -3061,12 +3063,14 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
30613063
for (matchRange, matchType) in allMatches {
30623064
// Add text before the match
30633065
if let range = Range(matchRange, in: contentText) {
3064-
let beforeText = String(contentText[lastEndIndex..<range.lowerBound])
3065-
if !beforeText.isEmpty {
3066-
var normalStyle = AttributeContainer()
3067-
normalStyle.font = .system(size: 14, design: .monospaced)
3068-
normalStyle.foregroundColor = isDark ? Color.white : Color.black
3069-
processedContent.append(AttributedString(beforeText).mergingAttributes(normalStyle))
3066+
if lastEndIndex < range.lowerBound {
3067+
let beforeText = String(contentText[lastEndIndex..<range.lowerBound])
3068+
if !beforeText.isEmpty {
3069+
var normalStyle = AttributeContainer()
3070+
normalStyle.font = .system(size: 14, design: .monospaced)
3071+
normalStyle.foregroundColor = isDark ? Color.white : Color.black
3072+
processedContent.append(AttributedString(beforeText).mergingAttributes(normalStyle))
3073+
}
30703074
}
30713075

30723076
// Add the match with appropriate styling
@@ -3084,7 +3088,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
30843088

30853089
processedContent.append(AttributedString(matchText).mergingAttributes(matchStyle))
30863090

3087-
lastEndIndex = range.upperBound
3091+
if lastEndIndex < range.upperBound { lastEndIndex = range.upperBound }
30883092
}
30893093
}
30903094

@@ -3484,19 +3488,23 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
34843488
// Regular expression to find @mentions
34853489
let pattern = "@([\\p{L}0-9_]+)"
34863490
let regex = try? NSRegularExpression(pattern: pattern, options: [])
3487-
let matches = regex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: contentText.count)) ?? []
3491+
let nsContent = contentText as NSString
3492+
let nsLen = nsContent.length
3493+
let matches = regex?.matches(in: contentText, options: [], range: NSRange(location: 0, length: nsLen)) ?? []
34883494

34893495
var lastEndIndex = contentText.startIndex
34903496

34913497
for match in matches {
34923498
// Add text before the mention
34933499
if let range = Range(match.range(at: 0), in: contentText) {
3494-
let beforeText = String(contentText[lastEndIndex..<range.lowerBound])
3495-
if !beforeText.isEmpty {
3496-
var normalStyle = AttributeContainer()
3497-
normalStyle.font = .system(size: 14, design: .monospaced)
3498-
normalStyle.foregroundColor = isDark ? Color.white : Color.black
3499-
processedContent.append(AttributedString(beforeText).mergingAttributes(normalStyle))
3500+
if lastEndIndex < range.lowerBound {
3501+
let beforeText = String(contentText[lastEndIndex..<range.lowerBound])
3502+
if !beforeText.isEmpty {
3503+
var normalStyle = AttributeContainer()
3504+
normalStyle.font = .system(size: 14, design: .monospaced)
3505+
normalStyle.foregroundColor = isDark ? Color.white : Color.black
3506+
processedContent.append(AttributedString(beforeText).mergingAttributes(normalStyle))
3507+
}
35003508
}
35013509

35023510
// Add the mention with highlight
@@ -3506,7 +3514,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
35063514
mentionStyle.foregroundColor = Color.orange
35073515
processedContent.append(AttributedString(mentionText).mergingAttributes(mentionStyle))
35083516

3509-
lastEndIndex = range.upperBound
3517+
if lastEndIndex < range.upperBound { lastEndIndex = range.upperBound }
35103518
}
35113519
}
35123520

@@ -4704,7 +4712,9 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
47044712
// Allow optional disambiguation suffix '#abcd' for duplicate nicknames
47054713
let pattern = "@([\\p{L}0-9_]+(?:#[a-fA-F0-9]{4})?)"
47064714
let regex = try? NSRegularExpression(pattern: pattern, options: [])
4707-
let matches = regex?.matches(in: content, options: [], range: NSRange(location: 0, length: content.count)) ?? []
4715+
let nsContent = content as NSString
4716+
let nsLen = nsContent.length
4717+
let matches = regex?.matches(in: content, options: [], range: NSRange(location: 0, length: nsLen)) ?? []
47084718

47094719
var mentions: [String] = []
47104720
let peerNicknames = meshService.getPeerNicknames()

0 commit comments

Comments
 (0)