feat: Remove replyToMessageId extraction and related logic from MessageRepository

This commit is contained in:
k1ngsterr1
2026-01-16 03:09:32 +05:00
parent 7e710a3160
commit 4b2f5785ae
2 changed files with 135 additions and 75 deletions

View File

@@ -662,21 +662,145 @@ object MessageCrypto {
/**
* Converts bytes to string mimicking JavaScript's Buffer.toString('utf-8') behavior.
* Invalid UTF-8 sequences are replaced with U+FFFD (replacement character).
* Uses WHATWG UTF-8 decoder algorithm where each invalid byte produces exactly ONE U+FFFD.
* This is critical for cross-platform compatibility with React Native!
*/
private fun bytesToJsUtf8String(bytes: ByteArray): String {
// CharsetDecoder with REPLACE action mimics JS behavior
val decoder = Charsets.UTF_8.newDecoder()
decoder.onMalformedInput(java.nio.charset.CodingErrorAction.REPLACE)
decoder.onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPLACE)
decoder.replaceWith("\uFFFD")
val result = StringBuilder()
var i = 0
return try {
decoder.decode(java.nio.ByteBuffer.wrap(bytes)).toString()
} catch (e: Exception) {
// Fallback to standard UTF-8 (shouldn't happen with REPLACE action)
String(bytes, Charsets.UTF_8)
while (i < bytes.size) {
val b0 = bytes[i].toInt() and 0xFF
when {
// ASCII (0x00-0x7F) - single byte
b0 <= 0x7F -> {
result.append(b0.toChar())
i++
}
// Continuation byte without starter (0x80-0xBF) - invalid
b0 <= 0xBF -> {
result.append('\uFFFD')
i++
}
// 2-byte sequence (0xC0-0xDF)
b0 <= 0xDF -> {
if (i + 1 >= bytes.size) {
// Truncated - emit replacement for this byte
result.append('\uFFFD')
i++
} else {
val b1 = bytes[i + 1].toInt() and 0xFF
if (b1 and 0xC0 != 0x80) {
// Invalid continuation - emit replacement for starter only
result.append('\uFFFD')
i++
} else {
val codePoint = ((b0 and 0x1F) shl 6) or (b1 and 0x3F)
// Check for overlong encoding (should be >= 0x80 for 2-byte)
if (codePoint < 0x80 || b0 == 0xC0 || b0 == 0xC1) {
// Overlong - emit replacement for each byte
result.append('\uFFFD')
result.append('\uFFFD')
} else {
result.append(codePoint.toChar())
}
i += 2
}
}
}
// 3-byte sequence (0xE0-0xEF)
b0 <= 0xEF -> {
if (i + 2 >= bytes.size) {
// Truncated
val remaining = bytes.size - i
repeat(remaining) { result.append('\uFFFD') }
i = bytes.size
} else {
val b1 = bytes[i + 1].toInt() and 0xFF
val b2 = bytes[i + 2].toInt() and 0xFF
if (b1 and 0xC0 != 0x80) {
// Invalid first continuation
result.append('\uFFFD')
i++
} else if (b2 and 0xC0 != 0x80) {
// Invalid second continuation - emit for first two bytes
result.append('\uFFFD')
result.append('\uFFFD')
i += 2
} else {
val codePoint = ((b0 and 0x0F) shl 12) or ((b1 and 0x3F) shl 6) or (b2 and 0x3F)
// Check for overlong (should be >= 0x800 for 3-byte) and surrogates
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
// Invalid - emit replacement for each byte
result.append('\uFFFD')
result.append('\uFFFD')
result.append('\uFFFD')
} else {
result.append(codePoint.toChar())
}
i += 3
}
}
}
// 4-byte sequence (0xF0-0xF7)
b0 <= 0xF7 -> {
if (i + 3 >= bytes.size) {
// Truncated
val remaining = bytes.size - i
repeat(remaining) { result.append('\uFFFD') }
i = bytes.size
} else {
val b1 = bytes[i + 1].toInt() and 0xFF
val b2 = bytes[i + 2].toInt() and 0xFF
val b3 = bytes[i + 3].toInt() and 0xFF
if (b1 and 0xC0 != 0x80) {
result.append('\uFFFD')
i++
} else if (b2 and 0xC0 != 0x80) {
result.append('\uFFFD')
result.append('\uFFFD')
i += 2
} else if (b3 and 0xC0 != 0x80) {
result.append('\uFFFD')
result.append('\uFFFD')
result.append('\uFFFD')
i += 3
} else {
val codePoint = ((b0 and 0x07) shl 18) or ((b1 and 0x3F) shl 12) or
((b2 and 0x3F) shl 6) or (b3 and 0x3F)
// Check for overlong (should be >= 0x10000) and max Unicode
if (codePoint < 0x10000 || codePoint > 0x10FFFF) {
result.append('\uFFFD')
result.append('\uFFFD')
result.append('\uFFFD')
result.append('\uFFFD')
} else {
// Encode as surrogate pair
val adjusted = codePoint - 0x10000
result.append((0xD800 + (adjusted shr 10)).toChar())
result.append((0xDC00 + (adjusted and 0x3FF)).toChar())
}
i += 4
}
}
}
// Invalid starter byte (0xF8-0xFF)
else -> {
result.append('\uFFFD')
i++
}
}
}
return result.toString()
}
/**

View File

@@ -284,9 +284,6 @@ class MessageRepository private constructor(private val context: Context) {
privateKey
)
// Извлекаем replyToMessageId из attachments
val replyToMessageId = extractReplyToMessageId(packet.attachments)
// 🔒 Шифруем plainMessage с использованием приватного ключа
val encryptedPlainMessage = CryptoManager.encryptWithPassword(plainText, privateKey)
@@ -304,7 +301,6 @@ class MessageRepository private constructor(private val context: Context) {
messageId = messageId, // 🔥 Используем сгенерированный messageId!
plainMessage = encryptedPlainMessage, // 🔒 Зашифрованный текст
attachments = attachmentsJson,
replyToMessageId = replyToMessageId, // 🔥 Добавляем replyToMessageId из attachments!
dialogKey = dialogKey
)
@@ -556,32 +552,6 @@ class MessageRepository private constructor(private val context: Context) {
plainMessage
}
// Десериализуем attachments из JSON
val attachmentsList = try {
if (attachments.isNotEmpty() && attachments != "[]") {
val jsonArray = JSONArray(attachments)
(0 until jsonArray.length()).mapNotNull { i ->
try {
val obj = jsonArray.getJSONObject(i)
MessageAttachment(
id = obj.optString("id", ""),
blob = obj.optString("blob", ""),
type = AttachmentType.fromInt(obj.optInt("type", 0)),
preview = obj.optString("preview", ""),
width = obj.optInt("width", 0),
height = obj.optInt("height", 0)
)
} catch (e: Exception) {
null
}
}
} else {
emptyList()
}
} catch (e: Exception) {
emptyList()
}
return Message(
id = id,
messageId = messageId,
@@ -592,7 +562,6 @@ class MessageRepository private constructor(private val context: Context) {
isFromMe = fromMe == 1,
isRead = read == 1,
deliveryStatus = DeliveryStatus.fromInt(delivered),
attachments = attachmentsList, // Десериализованные attachments
replyToMessageId = replyToMessageId
)
}
@@ -630,39 +599,6 @@ class MessageRepository private constructor(private val context: Context) {
return jsonArray.toString()
}
/**
* Извлечение replyToMessageId из attachments
* Ищет MESSAGES (type=1) attachment и парсит message_id
*/
private fun extractReplyToMessageId(attachments: List<MessageAttachment>): String? {
if (attachments.isEmpty()) return null
for (attachment in attachments) {
if (attachment.type == AttachmentType.MESSAGES) {
try {
// Берем blob или preview
val dataJson = attachment.blob.ifEmpty { attachment.preview }
if (dataJson.isEmpty() || dataJson.contains(":")) {
// Пропускаем зашифрованные старые сообщения
continue
}
val messagesArray = JSONArray(dataJson)
if (messagesArray.length() > 0) {
val replyMessage = messagesArray.getJSONObject(0)
val replyMessageId = replyMessage.optString("message_id", "")
if (replyMessageId.isNotEmpty()) {
return replyMessageId
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
return null
}
/**
* Сериализация attachments в JSON с расшифровкой MESSAGES blob
* Для MESSAGES типа blob расшифровывается и сохраняется в preview (как в RN)