Fix large message size calculation to use bytes.
This commit is contained in:
parent
e434cda40a
commit
16bb4d10d1
12 changed files with 225 additions and 119 deletions
|
@ -1052,8 +1052,10 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
|||
|
||||
if (hasExtraText(messageRecord)) {
|
||||
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
|
||||
bodyText.setMaxLength(messageRecord.getBody().length() - 2);
|
||||
} else {
|
||||
bodyText.setOverflowText(null);
|
||||
bodyText.setMaxLength(messageRecord.getBody().length());
|
||||
}
|
||||
|
||||
if (messageRecord.isOutgoing()) {
|
||||
|
|
|
@ -2,14 +2,10 @@ package org.thoughtcrime.securesms.conversation
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.CharacterCalculator
|
||||
import org.thoughtcrime.securesms.util.PushCharacterCalculator
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* The kinds of messages you can send, e.g. a plain Signal message, an SMS message, etc.
|
||||
|
@ -22,22 +18,14 @@ sealed class MessageSendType(
|
|||
val composeHintRes: Int,
|
||||
@DrawableRes
|
||||
val buttonDrawableRes: Int,
|
||||
@DrawableRes
|
||||
val menuDrawableRes: Int,
|
||||
@ColorRes
|
||||
val backgroundColorRes: Int,
|
||||
val transportType: TransportType,
|
||||
val characterCalculator: CharacterCalculator
|
||||
val maxBodyByteSize: Int
|
||||
) : Parcelable {
|
||||
|
||||
@get:JvmName("usesSignalTransport")
|
||||
val usesSignalTransport
|
||||
get() = transportType == TransportType.SIGNAL
|
||||
|
||||
fun calculateCharacters(body: String): CharacterCalculator.CharacterState {
|
||||
return characterCalculator.calculateCharacters(body)
|
||||
}
|
||||
|
||||
open fun getTitle(context: Context): String {
|
||||
return context.getString(titleRes)
|
||||
}
|
||||
|
@ -50,26 +38,12 @@ sealed class MessageSendType(
|
|||
titleRes = R.string.ConversationActivity_send_message_content_description,
|
||||
composeHintRes = R.string.conversation_activity__type_message_push,
|
||||
buttonDrawableRes = R.drawable.ic_send_lock_24,
|
||||
menuDrawableRes = R.drawable.ic_secure_24,
|
||||
backgroundColorRes = R.color.core_ultramarine,
|
||||
transportType = TransportType.SIGNAL,
|
||||
characterCalculator = PushCharacterCalculator()
|
||||
maxBodyByteSize = 2048
|
||||
)
|
||||
|
||||
enum class TransportType {
|
||||
SIGNAL,
|
||||
SMS
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getAllAvailable(): List<MessageSendType> {
|
||||
return listOf(SignalMessageSendType)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getFirstForTransport(transportType: TransportType): MessageSendType {
|
||||
return getAllAvailable().firstOrNull { it.transportType == transportType } ?: throw IllegalArgumentException("No options available for desired type $transportType!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1281,25 +1281,6 @@ class ConversationFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun calculateCharactersRemaining() {
|
||||
val messageBody: String = binding.conversationInputPanel.embeddedTextEditor.textTrimmed.toString()
|
||||
val charactersLeftView: TextView = binding.conversationInputSpaceLeft
|
||||
val characterState = MessageSendType.SignalMessageSendType.calculateCharacters(messageBody)
|
||||
|
||||
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
|
||||
charactersLeftView.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%d/%d (%d)",
|
||||
characterState.charactersRemaining,
|
||||
characterState.maxTotalMessageSize,
|
||||
characterState.messagesSpent
|
||||
)
|
||||
charactersLeftView.visibility = View.VISIBLE
|
||||
} else {
|
||||
charactersLeftView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerForResults() {
|
||||
addToContactsLauncher = registerForActivityResult(AddToContactsContract()) {}
|
||||
conversationActivityResultContracts = ConversationActivityResultContracts(this, ActivityResultCallbacks())
|
||||
|
@ -4020,7 +4001,6 @@ class ConversationFragment :
|
|||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
calculateCharactersRemaining()
|
||||
if (composeText.textTrimmed.isEmpty() || beforeLength == 0) {
|
||||
composeText.postDelayed({
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
|
||||
|
|
|
@ -40,7 +40,6 @@ import org.thoughtcrime.securesms.components.emoji.EmojiStrings
|
|||
import org.thoughtcrime.securesms.contactshare.Contact
|
||||
import org.thoughtcrime.securesms.contactshare.ContactUtil
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.GroupReviewState
|
||||
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.IndividualReviewState
|
||||
|
@ -186,8 +185,7 @@ class ConversationRepository(
|
|||
val sendCompletable = Completable.create { emitter ->
|
||||
val splitMessage: MessageUtil.SplitResult = MessageUtil.getSplitMessage(
|
||||
applicationContext,
|
||||
body,
|
||||
MessageSendType.SignalMessageSendType.calculateCharacters(body).maxPrimaryMessageSize
|
||||
body
|
||||
)
|
||||
|
||||
val outgoingMessageSlideDeck: SlideDeck? = splitMessage.textSlide.map {
|
||||
|
|
|
@ -169,7 +169,7 @@ class MediaSelectionRepository(context: Context) {
|
|||
)
|
||||
)
|
||||
} else {
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, sendType.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody)
|
||||
val splitBody = splitMessage.body
|
||||
|
||||
if (splitMessage.textSlide.isPresent) {
|
||||
|
@ -325,7 +325,7 @@ class MediaSelectionRepository(context: Context) {
|
|||
Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.contentType + "'. Skipping.")
|
||||
}
|
||||
}
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize)
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, body)
|
||||
val splitBody = splitMessage.body
|
||||
if (splitMessage.textSlide.isPresent) {
|
||||
slideDeck.addSlide(splitMessage.textSlide.get())
|
||||
|
|
|
@ -51,6 +51,7 @@ 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;
|
||||
|
@ -62,6 +63,8 @@ import java.util.Set;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import okio.Utf8;
|
||||
|
||||
/**
|
||||
* MultiShareSender encapsulates send logic (stolen from {@link org.thoughtcrime.securesms.conversation.ConversationActivity}
|
||||
* and provides a means to:
|
||||
|
@ -113,8 +116,7 @@ public final class MultiShareSender {
|
|||
List<Contact> contacts = multiShareArgs.getSharedContacts();
|
||||
SlideDeck slideDeck = new SlideDeck(primarySlideDeck);
|
||||
|
||||
boolean needsSplit = message != null &&
|
||||
message.length() > sendType.calculateCharacters(message).maxPrimaryMessageSize;
|
||||
boolean needsSplit = message != null && Utf8.size(message) > MessageUtil.MAX_MESSAGE_SIZE_BYTES;
|
||||
boolean hasMmsMedia = !multiShareArgs.getMedia().isEmpty() ||
|
||||
(multiShareArgs.getDataUri() != null && multiShareArgs.getDataUri() != Uri.EMPTY) ||
|
||||
multiShareArgs.getStickerLocator() != null ||
|
||||
|
@ -196,7 +198,7 @@ public final class MultiShareSender {
|
|||
{
|
||||
String body = multiShareArgs.getDraftText();
|
||||
if (sendType.usesSignalTransport() && body != null) {
|
||||
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body, sendType.calculateCharacters(body).maxPrimaryMessageSize);
|
||||
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(context, body);
|
||||
body = splitMessage.getBody();
|
||||
|
||||
if (splitMessage.getTextSlide().isPresent()) {
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MessageUtil {
|
||||
|
||||
private MessageUtil() {}
|
||||
|
||||
/**
|
||||
* @return If the message is longer than the allowed text size, this will return trimmed text with
|
||||
* an accompanying TextSlide. Otherwise it'll just return the original text.
|
||||
*/
|
||||
public static SplitResult getSplitMessage(@NonNull Context context, @NonNull String rawText, int maxPrimaryMessageSize) {
|
||||
String bodyText = rawText;
|
||||
Optional<TextSlide> textSlide = Optional.empty();
|
||||
|
||||
if (bodyText.length() > maxPrimaryMessageSize) {
|
||||
bodyText = rawText.substring(0, maxPrimaryMessageSize);
|
||||
|
||||
byte[] textData = rawText.getBytes();
|
||||
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date());
|
||||
String filename = String.format("signal-%s.txt", timestamp);
|
||||
Uri textUri = BlobProvider.getInstance()
|
||||
.forData(textData)
|
||||
.withMimeType(MediaUtil.LONG_TEXT)
|
||||
.withFileName(filename)
|
||||
.createForSingleSessionInMemory();
|
||||
|
||||
textSlide = Optional.of(new TextSlide(context, textUri, filename, textData.length));
|
||||
}
|
||||
|
||||
return new SplitResult(bodyText, textSlide);
|
||||
}
|
||||
|
||||
public static class SplitResult {
|
||||
private final String body;
|
||||
private final Optional<TextSlide> textSlide;
|
||||
|
||||
private SplitResult(@NonNull String body, @NonNull Optional<TextSlide> textSlide) {
|
||||
this.body = body;
|
||||
this.textSlide = textSlide;
|
||||
}
|
||||
|
||||
public @NonNull String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public @NonNull Optional<TextSlide> getTextSlide() {
|
||||
return textSlide;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.splitByByteLength
|
||||
import org.thoughtcrime.securesms.mms.TextSlide
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
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
|
||||
|
||||
/**
|
||||
* @return If the message is longer than the allowed text size, this will return trimmed text with
|
||||
* an accompanying TextSlide. Otherwise it'll just return the original text.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getSplitMessage(context: Context, rawText: String): SplitResult {
|
||||
val (trimmed, remainder) = rawText.splitByByteLength(MAX_MESSAGE_SIZE_BYTES)
|
||||
|
||||
return if (remainder != null) {
|
||||
val textData = rawText.toByteArray()
|
||||
val timestamp = SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(Date())
|
||||
val filename = String.format("signal-%s.txt", timestamp)
|
||||
val textUri = BlobProvider.getInstance()
|
||||
.forData(textData)
|
||||
.withMimeType(MediaUtil.LONG_TEXT)
|
||||
.withFileName(filename)
|
||||
.createForSingleSessionInMemory()
|
||||
|
||||
val textSlide = Optional.of(TextSlide(context, textUri, filename, textData.size.toLong()))
|
||||
|
||||
SplitResult(trimmed, textSlide)
|
||||
} else {
|
||||
SplitResult(trimmed, Optional.empty())
|
||||
}
|
||||
}
|
||||
|
||||
data class SplitResult(
|
||||
val body: String,
|
||||
val textSlide: Optional<TextSlide>
|
||||
)
|
||||
}
|
|
@ -5,11 +5,17 @@
|
|||
|
||||
package org.signal.core.util
|
||||
|
||||
import okio.utf8Size
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.net.URLEncoder
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.CharBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
private const val TAG: String = "StringExtensions"
|
||||
|
||||
/**
|
||||
* Treats the string as a serialized list of tokens and tells you if an item is present in the list.
|
||||
* In addition to exact matches, this handles wildcards at the end of an item.
|
||||
|
@ -79,3 +85,37 @@ fun CharSequence?.isNotNullOrBlank(): Boolean {
|
|||
fun String.urlEncode(): String {
|
||||
return URLEncoder.encode(this, StandardCharsets.UTF_8.name())
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a string into two parts, such that the first part will be at most [byteLength] bytes long.
|
||||
|
||||
* The first item of the pair will be the shortened string, and the second item will be the remainder.
|
||||
* Appending the two parts together will give you back the original string.
|
||||
*
|
||||
* If the input string is already less than [byteLength] bytes, the second item will be null.
|
||||
*/
|
||||
fun String.splitByByteLength(byteLength: Int): Pair<String, String?> {
|
||||
if (this.utf8Size() <= byteLength) {
|
||||
return this to null
|
||||
}
|
||||
|
||||
val charBuffer = CharBuffer.wrap(this)
|
||||
val encoder = Charsets.UTF_8.newEncoder()
|
||||
val outputBuffer = ByteBuffer.allocate(byteLength)
|
||||
|
||||
encoder.encode(charBuffer, outputBuffer, true)
|
||||
charBuffer.flip()
|
||||
|
||||
var firstPart = charBuffer.toString()
|
||||
|
||||
// Unfortunately some Android implementations will cause the charBuffer to go a step beyond what it should.
|
||||
// It's always extremely close (in testing, only ever off by 1), but as a workaround, we chop off characters
|
||||
// at the end until it fits. Bummer.
|
||||
while (firstPart.utf8Size() > byteLength) {
|
||||
Log.w(TAG, "Had to chop off a character to make it fit under the byte limit.")
|
||||
firstPart = firstPart.substring(0, firstPart.length - 1)
|
||||
}
|
||||
|
||||
val remainder = this.substring(firstPart.length)
|
||||
return firstPart to remainder
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
import okio.utf8Size
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class StringExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun `splitByByteLength fuzzing`() {
|
||||
val characterSet = "日月木山川田水火金土空海花風雨雪星森犬猫鳥魚虫人子女男友学校車電話本書時分先生愛夢楽音話語映画新古長短高低東西南北春夏秋冬雨雲星夜朝昼電気手足目耳口心頭体家国町村道橋山川本店仕事時間会話思考知識感情自動車飛行機船馬牛羊豚鶏鳥猫犬虎龍"
|
||||
|
||||
for (stringSize in 2100..2500) {
|
||||
for (byteLimit in 2000..2500) {
|
||||
val builder = StringBuilder()
|
||||
repeat(stringSize) {
|
||||
builder.append(characterSet.random())
|
||||
}
|
||||
val (trimmed, _) = builder.toString().splitByByteLength(byteLimit)
|
||||
assertTrue(trimmed.utf8Size() <= byteLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun idk() {
|
||||
val myString = """
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
すべての人間は生まれながらにして自由であり、尊厳と権利において平等である。彼らは理性と良心を授けられており、互いに兄弟愛の精神をもって行動しなければならない。
|
||||
|
||||
一方、現代社会において、技術の進歩は人々の生活を大きく変えつつある。情報の流通は瞬時に行われ、距離や時間の壁を越えて人々がつながる世界が現実となっている。しかし、これに伴い新たな課題も生じており、個人のプライバシーや倫理の問題が議論の中心となっている。
|
||||
|
||||
経済の発展と共に、都市化が進む一方で、自然環境の破壊も深刻な問題となっている。持続可能な社会を目指すためには、私たち一人ひとりが責任を持ち、資源を大切にする意識を持つことが重要である。
|
||||
|
||||
文化や伝統は社会の根底を支えるものであり、時代が変わってもその価値は変わらない。古くから伝わる知恵や習慣は、現代においても新たな意味を持ち続けるだろう。
|
||||
""".trimIndent()
|
||||
|
||||
val (trimmed, _) = myString.splitByByteLength(2048)
|
||||
assertTrue(trimmed.utf8Size() <= 2048)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.signal.core.util
|
||||
|
||||
import okio.utf8Size
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(Parameterized::class)
|
||||
class StringExtensions_splitByByteLength(
|
||||
private val testInput: String,
|
||||
private val byteLength: Int,
|
||||
private val expected: Pair<String, String?>
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun testModelInList() {
|
||||
val actual = testInput.splitByByteLength(byteLength)
|
||||
assertEquals(expected, actual)
|
||||
assertTrue(actual.first.utf8Size() <= byteLength)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{index}: splitByByteLength(input={0}, byteLength={1})")
|
||||
fun data(): List<Array<Any>> {
|
||||
return listOf<Array<Any>>(
|
||||
arrayOf("1234567890", 0, "" to "1234567890"),
|
||||
arrayOf("1234567890", 3, "123" to "4567890"),
|
||||
arrayOf("1234567890", 10, "1234567890" to null),
|
||||
arrayOf("1234567890", 15, "1234567890" to null),
|
||||
arrayOf("大いなる力には大いなる責任が伴う", 0, "" to "大いなる力には大いなる責任が伴う"),
|
||||
arrayOf("大いなる力には大いなる責任が伴う", 8, "大い" to "なる力には大いなる責任が伴う"),
|
||||
arrayOf("大いなる力には大いなる責任が伴う", 47, "大いなる力には大いなる責任が伴" to "う"),
|
||||
arrayOf("大いなる力には大いなる責任が伴う", 48, "大いなる力には大いなる責任が伴う" to null),
|
||||
arrayOf("大いなる力には大いなる責任が伴う", 100, "大いなる力には大いなる責任が伴う" to null)
|
||||
).toList()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -131,6 +131,7 @@ import org.whispersystems.signalservice.internal.util.Util;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -155,6 +156,7 @@ import io.reactivex.rxjava3.exceptions.CompositeException;
|
|||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Unit;
|
||||
import okio.ByteString;
|
||||
import okio.Utf8;
|
||||
|
||||
/**
|
||||
* The main interface for sending Signal Service messages.
|
||||
|
@ -1007,6 +1009,10 @@ public class SignalServiceMessageSender {
|
|||
Content.Builder container = new Content.Builder();
|
||||
DataMessage.Builder dataMessage = createDataMessage(message);
|
||||
|
||||
if (dataMessage.body != null && Utf8.size(dataMessage.body) > 2048) {
|
||||
throw new ContentTooLargeException(Utf8.size(dataMessage.body));
|
||||
}
|
||||
|
||||
return enforceMaxContentSize(container.dataMessage(dataMessage.build()).build());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue