Add various backup fixes for polls.

This commit is contained in:
Michelle Tang
2025-10-29 09:54:40 -04:00
committed by jeffrey-signal
parent 3f5a4ebf7b
commit 47201f4955
39 changed files with 45 additions and 7 deletions

View File

@@ -123,6 +123,14 @@ object ExportSkips {
return log(sentTimestamp, "Poll terminate update was empty.")
}
fun invalidPollQuestion(sentTimestamp: Long): String {
return log(sentTimestamp, "Poll question was invalid.")
}
fun invalidPollOption(sentTimestamp: Long): String {
return log(sentTimestamp, "Poll option was invalid.")
}
fun individualChatUpdateInWrongTypeOfChat(sentTimestamp: Long): String {
return log(sentTimestamp, "A chat update that only makes sense for individual chats was found in a different kind of chat.")
}

View File

@@ -117,6 +117,8 @@ private val TAG = Log.tag(ChatItemArchiveExporter::class.java)
private val MAX_INLINED_BODY_SIZE = 128.kibiBytes.bytes.toInt()
private val MAX_INLINED_BODY_SIZE_WITH_LONG_ATTACHMENT_POINTER = 2.kibiBytes.bytes.toInt()
private val MAX_INLINED_QUOTE_BODY_SIZE = 2.kibiBytes.bytes.toInt()
private const val MAX_POLL_CHARACTER_LENGTH = 100
private const val MAX_POLL_OPTIONS = 10
/**
* An iterator for chat items with a clever performance twist: rather than do the extra queries one at a time (for reactions,
@@ -386,7 +388,15 @@ class ChatItemArchiveExporter(
extraData.pollsById[record.id] != null -> {
val poll = extraData.pollsById[record.id]!!
builder.poll = poll.toRemotePollMessage()
if (poll.question.isEmpty() || poll.question.length > MAX_POLL_CHARACTER_LENGTH) {
Log.w(TAG, ExportSkips.invalidPollQuestion(record.dateSent))
continue
}
if (poll.pollOptions.isEmpty() || poll.pollOptions.size > MAX_POLL_OPTIONS || poll.pollOptions.any { it.text.isEmpty() || it.text.length > MAX_POLL_CHARACTER_LENGTH }) {
Log.w(TAG, ExportSkips.invalidPollOption(record.dateSent))
continue
}
builder.poll = poll.toRemotePollMessage(reactionRecords = extraData.reactionsById[record.id])
transformTimer.emit("poll")
}
@@ -492,7 +502,7 @@ class ChatItemArchiveExporter(
val pollsFuture = executor.submitTyped {
extraDataTimer.timeEvent("polls") {
db.pollTable.getPollsForMessages(messageIds)
db.pollTable.getPollsForMessages(messageIds = messageIds, includePending = false)
}
}
@@ -1167,7 +1177,7 @@ private fun BackupMessageRecord.toRemoteGiftBadgeUpdate(): BackupGiftBadge? {
)
}
private fun PollRecord.toRemotePollMessage(): Poll {
private fun PollRecord.toRemotePollMessage(reactionRecords: List<ReactionRecord>?): Poll {
return Poll(
question = this.question,
allowMultiple = this.allowMultipleVotes,
@@ -1182,7 +1192,8 @@ private fun PollRecord.toRemotePollMessage(): Poll {
)
}
)
}
},
reactions = reactionRecords?.toRemote() ?: emptyList()
)
}

View File

@@ -693,6 +693,7 @@ class ChatItemArchiveImporter(
this.stickerMessage != null -> this.stickerMessage.reactions
this.viewOnceMessage != null -> this.viewOnceMessage.reactions
this.directStoryReplyMessage != null -> this.directStoryReplyMessage.reactions
this.poll != null -> this.poll.reactions
else -> emptyList()
}

View File

@@ -607,7 +607,7 @@ class PollTables(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
/**
* Maps message ids to its associated poll (if it exists)
*/
fun getPollsForMessages(messageIds: Collection<Long>): Map<Long, PollRecord> {
fun getPollsForMessages(messageIds: Collection<Long>, includePending: Boolean = true): Map<Long, PollRecord> {
if (messageIds.isEmpty() || !Recipient.isSelfSet) {
return emptyMap()
}
@@ -625,9 +625,9 @@ class PollTables(context: Context?, databaseHelper: SignalDatabase?) : DatabaseT
val (pendingAdds, pendingRemoves) = getPendingVotes(pollId)
val pollOptions = getPollOptions(pollId).map { option ->
val voters = pollVotes[option.key] ?: emptyList()
val voteState = if (pendingAdds.contains(option.key)) {
val voteState = if (includePending && pendingAdds.contains(option.key)) {
VoteState.PENDING_ADD
} else if (pendingRemoves.contains(option.key)) {
} else if (includePending && pendingRemoves.contains(option.key)) {
VoteState.PENDING_REMOVE
} else if (voters.any { it.id == self }) {
VoteState.ADDED

View File

@@ -120,6 +120,8 @@ import kotlin.time.Duration.Companion.seconds
object DataMessageProcessor {
private const val BODY_RANGE_PROCESSING_LIMIT = 250
private const val POLL_CHARACTER_LIMIT = 100
private const val POLL_OPTIONS_LIMIT = 10
fun process(
context: Context,
@@ -1079,6 +1081,16 @@ object DataMessageProcessor {
return null
}
if (poll.question == null || poll.question!!.isEmpty() || poll.question!!.length > POLL_CHARACTER_LIMIT) {
warn(envelope.timestamp!!, "[handlePollCreate] Poll question is invalid.")
return null
}
if (poll.options.isEmpty() || poll.options.size > POLL_OPTIONS_LIMIT || poll.options.any { it.isEmpty() || it.length > POLL_CHARACTER_LIMIT }) {
warn(envelope.timestamp!!, "[handlePollCreate] Poll option is invalid.")
return null
}
val pollMessage = IncomingMessage(
type = MessageType.NORMAL,
from = senderRecipient.id,