From 9bb3505277b8132c3fd0be52d9bbbc6078723ff2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 3 Jan 2023 11:22:42 -0300 Subject: [wallet] Don't allow entering invalid amounts #0007350 --- taler-kotlin-android/build.gradle | 4 +- .../src/main/java/net/taler/common/Amount.kt | 44 +++++++++++++++++----- .../java/net/taler/wallet/ReceiveFundsFragment.kt | 4 +- .../java/net/taler/wallet/SendFundsFragment.kt | 4 +- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/taler-kotlin-android/build.gradle b/taler-kotlin-android/build.gradle index efd305f..69b4f4d 100644 --- a/taler-kotlin-android/build.gradle +++ b/taler-kotlin-android/build.gradle @@ -21,6 +21,7 @@ plugins { } android { + namespace 'net.taler.common' compileSdkVersion 32 buildToolsVersion "$build_tools_version" @@ -49,9 +50,6 @@ android { excludes += ['META-INF/*.kotlin_module'] } } - - namespace 'net.taler.common' - } dependencies { diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt index 4a6e4b3..750a1de 100644 --- a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt +++ b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt @@ -25,8 +25,11 @@ import kotlin.math.floor import kotlin.math.pow import kotlin.math.roundToInt -public class AmountParserException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause) -public class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause) +public class AmountParserException(msg: String? = null, cause: Throwable? = null) : + Exception(msg, cause) + +public class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : + Exception(msg, cause) @Serializable(with = KotlinXAmountSerializer::class) public data class Amount( @@ -50,7 +53,7 @@ public data class Amount( * of the base currency value. For example, a fraction * of 50_000_000 would correspond to 50 cents. */ - val fraction: Int + val fraction: Int, ) : Comparable { public companion object { @@ -63,8 +66,8 @@ public data class Amount( public const val MAX_FRACTION: Int = 99_999_999 public fun fromDouble(currency: String, value: Double): Amount { - val intPart = Math.floor(value).toLong() - val fraPart = Math.floor((value - intPart) * FRACTIONAL_BASE).toInt() + val intPart = floor(value).toLong() + val fraPart = floor((value - intPart) * FRACTIONAL_BASE).toInt() return Amount(currency, intPart, fraPart) } @@ -87,14 +90,34 @@ public data class Amount( val fractionStr = valueSplit[1] if (fractionStr.length > MAX_FRACTION_LENGTH) throw AmountParserException("Fraction $fractionStr too long") - val fraction = "0.$fractionStr".toDoubleOrNull() - ?.times(FRACTIONAL_BASE) - ?.roundToInt() - checkFraction(fraction) + checkFraction(fractionStr.getFraction()) } else 0 return Amount(checkCurrency(currency), value, fraction) } + public fun isValidAmountStr(str: String): Boolean { + val split = str.split(".") + try { + checkValue(split[0].toLongOrNull()) + } catch (e: AmountParserException) { + return false + } + // also check fraction, if it exists + if (split.size > 1) { + val fractionStr = split[1] + if (fractionStr.length > MAX_FRACTION_LENGTH) return false + val fraction = fractionStr.getFraction() ?: return false + return fraction <= MAX_FRACTION + } + return true + } + + private fun String.getFraction(): Int? { + return "0.$this".toDoubleOrNull() + ?.times(FRACTIONAL_BASE) + ?.roundToInt() + } + public fun min(currency: String): Amount = Amount(currency, 0, 1) public fun max(currency: String): Amount = Amount(currency, MAX_VALUE, MAX_FRACTION) @@ -132,7 +155,8 @@ public data class Amount( public operator fun plus(other: Amount): Amount { check(currency == other.currency) { "Can only subtract from same currency" } - val resultValue = value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong() + val resultValue = + value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong() if (resultValue > MAX_VALUE) throw AmountOverflowException() val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE diff --git a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt index 647512c..8ae96ad 100644 --- a/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/ReceiveFundsFragment.kt @@ -55,6 +55,7 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import net.taler.common.Amount +import net.taler.common.Amount.Companion.isValidAmountStr import net.taler.wallet.exchanges.ExchangeItem class ReceiveFundsFragment : Fragment() { @@ -136,7 +137,8 @@ private fun ReceiveFundsIntro( keyboardOptions = KeyboardOptions.Default.copy(keyboardType = Decimal), onValueChange = { input -> isError = false - text = input.filter { it.isDigit() || it == '.' } + val filtered = input.filter { it.isDigit() || it == '.' } + if (filtered.endsWith('.') || isValidAmountStr(filtered)) text = filtered }, isError = isError, label = { diff --git a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt index 640fbf7..c5348a3 100644 --- a/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/SendFundsFragment.kt @@ -52,6 +52,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import net.taler.common.Amount +import net.taler.common.Amount.Companion.isValidAmountStr class SendFundsFragment : Fragment() { private val model: MainViewModel by activityViewModels() @@ -126,7 +127,8 @@ private fun SendFundsIntro( onValueChange = { input -> isError = false insufficientBalance = false - text = input.filter { it.isDigit() || it == '.' } + val filtered = input.filter { it.isDigit() || it == '.' } + if (filtered.endsWith('.') || isValidAmountStr(filtered)) text = filtered }, isError = isError || insufficientBalance, label = { -- cgit v1.2.3