Truncate message length based on utf8-byte size.

This commit is contained in:
Greyson Parrelli
2025-07-18 16:27:41 -04:00
committed by GitHub
parent 84c6719d03
commit 1cef53d82e
14 changed files with 500 additions and 9 deletions

View File

@@ -92,6 +92,7 @@ import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.signal.core.util.ByteLimitInputFilter
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.Result
import org.signal.core.util.ThreadUtil
@@ -319,6 +320,7 @@ import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
import org.thoughtcrime.securesms.util.MessageConstraintsUtil.getEditMessageThresholdHours
import org.thoughtcrime.securesms.util.MessageConstraintsUtil.isValidEditMessageSend
import org.thoughtcrime.securesms.util.MessageUtil
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SignalLocalMetrics
@@ -405,7 +407,7 @@ class ConversationFragment :
}
private val disposables = LifecycleDisposable()
private val binding by ViewBinderDelegate(V2ConversationFragmentBinding::bind) { _binding ->
private val binding by ViewBinderDelegate(bindingFactory = V2ConversationFragmentBinding::bind, onBindingWillBeDestroyed = { _binding ->
_binding.conversationInputPanel.embeddedTextEditor.apply {
setOnEditorActionListener(null)
setCursorPositionChangedListener(null)
@@ -428,7 +430,7 @@ class ConversationFragment :
_binding.conversationItemRecycler.adapter = null
textDraftSaveDebouncer.clear()
}
})
private val viewModel: ConversationViewModel by viewModel {
ConversationViewModel(
@@ -1013,6 +1015,7 @@ class ConversationFragment :
setStylingChangedListener(composeTextEventsListener)
setOnClickListener(composeTextEventsListener)
onFocusChangeListener = composeTextEventsListener
filters += ByteLimitInputFilter(MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES)
}
sendButton.apply {

View File

@@ -31,6 +31,9 @@ import org.thoughtcrime.securesms.net.NotPushRegisteredException
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.AttachmentProgressService
import org.thoughtcrime.securesms.transport.UndeliverableMessageException
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageUtil
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
@@ -137,6 +140,10 @@ class AttachmentUploadJob private constructor(
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId) ?: throw InvalidAttachmentException("Cannot find the specified attachment.")
if (MediaUtil.isLongTextType(databaseAttachment.contentType) && databaseAttachment.size > MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES) {
throw UndeliverableMessageException("Long text attachment is too long! Max size: ${MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES} bytes, Actual size: ${databaseAttachment.size} bytes.")
}
val timeSinceUpload = System.currentTimeMillis() - databaseAttachment.uploadTimestamp
if (timeSinceUpload < UPLOAD_REUSE_THRESHOLD && !TextUtils.isEmpty(databaseAttachment.remoteLocation)) {
Log.i(TAG, "We can re-use an already-uploaded file. It was uploaded $timeSinceUpload ms (${timeSinceUpload.milliseconds.inRoundedDays()} days) ago. Skipping.")

View File

@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MessageUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
@@ -60,6 +61,8 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import okio.Utf8;
public class IndividualSendJob extends PushSendJob {
public static final String KEY = "PushMediaSendJob";
@@ -251,6 +254,10 @@ public class IndividualSendJob extends PushSendJob {
throw new UndeliverableMessageException("No destination address.");
}
if (Utf8.size(message.getBody()) > MessageUtil.MAX_INLINE_BODY_SIZE_BYTES) {
throw new UndeliverableMessageException("The total body size was greater than our limit of " + MessageUtil.MAX_INLINE_BODY_SIZE_BYTES + " bytes.");
}
try {
rotateSenderCertificateIfNecessary();

View File

@@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MessageUtil;
import org.thoughtcrime.securesms.util.RecipientAccessList;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.Util;
@@ -73,6 +74,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import okio.ByteString;
import okio.Utf8;
public final class PushGroupSendJob extends PushSendJob {
@@ -266,6 +268,10 @@ public final class PushGroupSendJob extends PushSendJob {
private List<SendMessageResult> deliver(OutgoingMessage message, @Nullable MessageRecord originalEditedMessage, @NonNull Recipient groupRecipient, @NonNull List<Recipient> destinations)
throws IOException, UntrustedIdentityException, UndeliverableMessageException
{
if (Utf8.size(message.getBody()) >= MessageUtil.MAX_INLINE_BODY_SIZE_BYTES) {
throw new UndeliverableMessageException("The total body size was greater than our limit of " + MessageUtil.MAX_INLINE_BODY_SIZE_BYTES + " bytes.");
}
try {
rotateSenderCertificateIfNecessary();

View File

@@ -14,6 +14,7 @@ import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.signal.core.util.ByteLimitInputFilter
import org.signal.core.util.EditTextUtil
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout
@@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.Stories
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.views.Stub
import org.thoughtcrime.securesms.util.visible
@@ -92,6 +94,7 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a
binding.content.addAMessageInput.setText(requireArguments().getCharSequence(ARG_INITIAL_TEXT))
binding.content.addAMessageInput.addTextChangedListener { viewModel.setMessage(it) }
binding.content.addAMessageInput.filters += ByteLimitInputFilter(MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES)
binding.content.emojiToggle.setOnClickListener { onEmojiToggleClicked() }
if (requireArguments().getBoolean(ARG_INITIAL_EMOJI_TOGGLE) && view is KeyboardAwareLinearLayout) {

View File

@@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MessageUtil;
import org.thoughtcrime.securesms.util.Util;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -116,7 +115,7 @@ public final class MultiShareSender {
List<Contact> contacts = multiShareArgs.getSharedContacts();
SlideDeck slideDeck = new SlideDeck(primarySlideDeck);
boolean needsSplit = message != null && Utf8.size(message) > MessageUtil.MAX_MESSAGE_SIZE_BYTES;
boolean needsSplit = message != null && Utf8.size(message) > MessageUtil.MAX_INLINE_BODY_SIZE_BYTES;
boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() ||
(multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) ||
multiShareArgs.getStickerLocator() != null ||

View File

@@ -16,6 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import org.signal.core.util.ByteLimitInputFilter
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ComposeText
@@ -34,6 +35,7 @@ import org.thoughtcrime.securesms.keyboard.emoji.toMappingModels
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MessageUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
@@ -86,6 +88,7 @@ class StoryReplyComposer @JvmOverloads constructor(
else -> false
}
}
input.filters += ByteLimitInputFilter(MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES)
anyReactionView.setOnClickListener {
callback?.onPickAnyReactionClicked()

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import org.signal.core.util.kibiBytes
import org.signal.core.util.splitByByteLength
import org.thoughtcrime.securesms.mms.TextSlide
import org.thoughtcrime.securesms.providers.BlobProvider
@@ -10,7 +11,13 @@ import java.util.Locale
import java.util.Optional
object MessageUtil {
const val MAX_MESSAGE_SIZE_BYTES: Int = 2000 // Technically 2048, but we'll play it a little safe
/** The maximum size of an inlined text body we'll allow in a proto. Anything larger than this will need to be a long-text attachment. */
@JvmField
val MAX_INLINE_BODY_SIZE_BYTES: Int = 2.kibiBytes.bytes.toInt()
/** The maximum total message size we'll allow ourselves to send, even as a long text attachment. */
@JvmField
val MAX_TOTAL_BODY_SIZE_BYTES = 64.kibiBytes.bytes.toInt()
/**
* @return If the message is longer than the allowed text size, this will return trimmed text with
@@ -18,7 +25,7 @@ object MessageUtil {
*/
@JvmStatic
fun getSplitMessage(context: Context, rawText: String): SplitResult {
val (trimmed, remainder) = rawText.splitByByteLength(MAX_MESSAGE_SIZE_BYTES)
val (trimmed, remainder) = rawText.splitByByteLength(MAX_INLINE_BODY_SIZE_BYTES)
return if (remainder != null) {
val textData = rawText.toByteArray()