From 41baf24af56786ea2b1eaff0dafceed04a3eebc5 Mon Sep 17 00:00:00 2001 From: kate-signal Date: Wed, 22 Oct 2025 16:18:35 -0400 Subject: [PATCH] Handle spaces in poll create better --- .../Polls/NewPollViewController.swift | 14 +++++-- SignalServiceKit/Environment/AppSetup.swift | 1 + .../Polls/PollMessageManager.swift | 42 +++++++++++++++++++ .../Messages/MessageReceiver.swift | 17 ++++---- .../tests/Polls/PollManagerTest.swift | 1 + 5 files changed, 62 insertions(+), 13 deletions(-) diff --git a/Signal/src/ViewControllers/Polls/NewPollViewController.swift b/Signal/src/ViewControllers/Polls/NewPollViewController.swift index 606e717f72..ef6d0abacf 100644 --- a/Signal/src/ViewControllers/Polls/NewPollViewController.swift +++ b/Signal/src/ViewControllers/Polls/NewPollViewController.swift @@ -181,6 +181,9 @@ struct NewPollView: View { if newText.count > characterLimit { pollQuestion = String(newText.prefix(characterLimit)) } + if pollQuestion.stripped.isEmpty { + pollQuestion = "" + } } } header: { Text( @@ -200,6 +203,9 @@ struct NewPollView: View { if newText.count > characterLimit { option.text = String(newText.prefix(characterLimit)) } + if option.text.stripped.isEmpty { + option.text = "" + } } .focused($focusedItemID, equals: option.id) } @@ -284,8 +290,10 @@ struct NewPollView: View { private func sendButtonPressed(sendButtonEnabled: Bool) { if sendButtonEnabled { viewModel.onSend( - pollOptions: pollOptions.map(\.text).filter { !$0.isEmpty }, - question: pollQuestion, + pollOptions: pollOptions.map(\.text) + .filter { !$0.stripped.isEmpty } + .map { $0.stripped }, + question: pollQuestion.stripped, allowMultipleVotes: allowMultipleVotes ) } else { @@ -299,7 +307,7 @@ struct NewPollView: View { private func onChange() { // Filter out all blank fields since user may have deleted a middle option. - var filteredPollOptions = pollOptions.filter({ !$0.text.isEmpty }) + var filteredPollOptions = pollOptions.filter({ !$0.text.stripped.isEmpty }) if filteredPollOptions.count >= 10 { return diff --git a/SignalServiceKit/Environment/AppSetup.swift b/SignalServiceKit/Environment/AppSetup.swift index 51a48fe741..a4f28503a8 100644 --- a/SignalServiceKit/Environment/AppSetup.swift +++ b/SignalServiceKit/Environment/AppSetup.swift @@ -1365,6 +1365,7 @@ extension AppSetup.GlobalsContinuation { accountManager: tsAccountManager, messageSenderJobQueue: messageSenderJobQueue, disappearingMessagesConfigurationStore: disappearingMessagesConfigurationStore, + attachmentContentValidator: attachmentContentValidator, db: db ) let pollArchiver = BackupArchivePollArchiver( diff --git a/SignalServiceKit/Messages/Interactions/Polls/PollMessageManager.swift b/SignalServiceKit/Messages/Interactions/Polls/PollMessageManager.swift index cf6ac7d971..812f707c5a 100644 --- a/SignalServiceKit/Messages/Interactions/Polls/PollMessageManager.swift +++ b/SignalServiceKit/Messages/Interactions/Polls/PollMessageManager.swift @@ -32,6 +32,7 @@ public class PollMessageManager { private let messageSenderJobQueue: MessageSenderJobQueue private let accountManager: TSAccountManager private let disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore + private let attachmentContentValidator: AttachmentContentValidator init( pollStore: PollStore, @@ -40,6 +41,7 @@ public class PollMessageManager { accountManager: TSAccountManager, messageSenderJobQueue: MessageSenderJobQueue, disappearingMessagesConfigurationStore: DisappearingMessagesConfigurationStore, + attachmentContentValidator: AttachmentContentValidator, db: DB ) { self.pollStore = pollStore @@ -49,6 +51,46 @@ public class PollMessageManager { self.messageSenderJobQueue = messageSenderJobQueue self.db = db self.disappearingMessagesConfigurationStore = disappearingMessagesConfigurationStore + self.attachmentContentValidator = attachmentContentValidator + } + + public func validateIncomingPollCreate( + pollCreate: SSKProtoDataMessagePollCreate, + tx: DBWriteTransaction + ) throws -> ValidatedInlineMessageBody { + guard let question = pollCreate.question else { + throw OWSAssertionError("Poll missing question") + } + guard question.trimmedIfNeeded(maxByteCount: OWSMediaUtils.kOversizeTextMessageSizeThresholdBytes) == nil + && question.count <= OWSPoll.Constants.maxCharacterLength + else { + throw OWSAssertionError("Poll question too large") + } + + guard question.count > 0 else { + throw OWSAssertionError("Poll question empty") + } + + guard pollCreate.options.count >= 2 else { + throw OWSAssertionError("Poll does not have enough options") + } + + for option in pollCreate.options { + guard option.trimmedIfNeeded(maxByteCount: OWSMediaUtils.kOversizeTextMessageSizeThresholdBytes) == nil + && option.count <= OWSPoll.Constants.maxCharacterLength + else { + throw OWSAssertionError("Poll option too large") + } + + guard option.count > 0 else { + throw OWSAssertionError("Poll option empty") + } + } + + return self.attachmentContentValidator.truncatedMessageBodyForInlining( + MessageBody(text: question, ranges: .empty), + tx: tx + ) } public func processIncomingPollCreate( diff --git a/SignalServiceKit/Messages/MessageReceiver.swift b/SignalServiceKit/Messages/MessageReceiver.swift index c8e93d7134..dcdaa55f04 100644 --- a/SignalServiceKit/Messages/MessageReceiver.swift +++ b/SignalServiceKit/Messages/MessageReceiver.swift @@ -1151,18 +1151,15 @@ public final class MessageReceiver { } let pollCreate = dataMessage.pollCreate - if let pollCreate, let question = pollCreate.question { - guard question.count <= OWSPoll.Constants.maxCharacterLength - && question.trimmedIfNeeded(maxByteCount: OWSMediaUtils.kOversizeTextMessageSizeThresholdBytes) == nil - else { - owsFailDebug("Poll question too large") + if let pollCreate { + do { + body = try DependenciesBridge.shared.pollMessageManager.validateIncomingPollCreate( + pollCreate: pollCreate, + tx: tx) + } catch { + Logger.error("Error validating incoming poll create: \(error)") return nil } - - body = DependenciesBridge.shared.attachmentContentValidator.truncatedMessageBodyForInlining( - MessageBody(text: question, ranges: .empty), - tx: tx - ) } if let pollTerminate = dataMessage.pollTerminate{ diff --git a/SignalServiceKit/tests/Polls/PollManagerTest.swift b/SignalServiceKit/tests/Polls/PollManagerTest.swift index 72275cbe38..556632580b 100644 --- a/SignalServiceKit/tests/Polls/PollManagerTest.swift +++ b/SignalServiceKit/tests/Polls/PollManagerTest.swift @@ -27,6 +27,7 @@ struct PollManagerTest { accountManager: mockTSAccountManager, messageSenderJobQueue: MessageSenderJobQueue(appReadiness: AppReadinessMock()), disappearingMessagesConfigurationStore: MockDisappearingMessagesConfigurationStore(), + attachmentContentValidator: AttachmentContentValidatorMock(), db: db ) let testPhone = E164("+16505550101")!