From ff217b061f0d1893276baddb6cece977dae4505c Mon Sep 17 00:00:00 2001 From: abgolor Date: Fri, 26 Jun 2026 17:31:40 +0000 Subject: [PATCH] Prevent entering oversized custom donation amounts. Fixes #14750 Resolves #14854 Co-authored-by: Greyson Parrelli --- .../settings/app/subscription/boost/Boost.kt | 10 +++++ .../boost/BoostTest__MoneyFilter.kt | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/Boost.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/Boost.kt index 33f093b82d..4380b59077 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/Boost.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/Boost.kt @@ -226,6 +226,10 @@ data class Boost( @VisibleForTesting class MoneyFilter(val currency: Currency, private val text: AppCompatEditText? = null, private val onCustomAmountChanged: (String) -> Unit = {}) : DigitsKeyListener(false, true), TextWatcher { + companion object { + private const val MAX_INTEGRAL_DIGITS = 10 + } + val separator = DecimalFormatSymbols.getInstance().decimalSeparator val separatorCount = min(1, currency.defaultFractionDigits) val symbol: String = currency.getSymbol(Locale.getDefault()) @@ -257,6 +261,12 @@ data class Boost( val result = dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length) val resultWithoutCurrencyPrefix = BidiUtil.stripBidiIndicator(result.removePrefix(symbol).removeSuffix(symbol).trim()) + val integralDigitCount = resultWithoutCurrencyPrefix.substringBefore(separator).count { it.isDigit() } + + if (integralDigitCount > MAX_INTEGRAL_DIGITS) { + return dest.subSequence(dstart, dend) + } + if (resultWithoutCurrencyPrefix.length == 1 && !resultWithoutCurrencyPrefix.isDigitsOnly() && resultWithoutCurrencyPrefix != separator.toString()) { return dest.subSequence(dstart, dend) } diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostTest__MoneyFilter.kt b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostTest__MoneyFilter.kt index 1c109e55ab..ceee596562 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostTest__MoneyFilter.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/boost/BoostTest__MoneyFilter.kt @@ -247,4 +247,47 @@ class BoostTest__MoneyFilter { assertEquals("0.0", result) } + + @Test + fun `Given USD, when I enter ten integral digits, then I expect successful filter`() { + val testSubject = Boost.MoneyFilter(usd) + val editable = SpannableStringBuilder("1234567890.12") + val dest = SpannableStringBuilder() + + val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0) + + assertNull(filterResult) + } + + @Test + fun `Given USD, when I enter eleven integral digits, then I expect unsuccessful filter`() { + val testSubject = Boost.MoneyFilter(usd) + val editable = SpannableStringBuilder("12345678901") + val dest = SpannableStringBuilder() + + val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0) + + assertNotNull(filterResult) + } + + @Test + fun `Given USD and ten integral digits, when I append a digit, then I expect the existing text to be retained`() { + val testSubject = Boost.MoneyFilter(usd) + val dest = SpannableStringBuilder("$1234567890") + + val filterResult = testSubject.filter("1", 0, 1, dest, dest.length, dest.length) + + assertEquals("", filterResult.toString()) + } + + @Test + fun `Given JPY, when I enter eleven integral digits, then I expect unsuccessful filter`() { + val testSubject = Boost.MoneyFilter(yen) + val editable = SpannableStringBuilder("12345678901") + val dest = SpannableStringBuilder() + + val filterResult = testSubject.filter(editable, 0, editable.length, dest, 0, 0) + + assertNotNull(filterResult) + } }