Skip to content

Commit 9704a20

Browse files
gbirch-stripecodex
andauthored
Fix UnknownFormatConversionException (#13162)
* Fix UnknownFormatConversionException when user input contains percent signs StaticResolvableString.resolve() called String.format(value, ...) unconditionally, treating the value as a format string. When the value is raw user input (e.g. from a text field whose contentDescription is built via String.resolvableString), any percent sign sequence such as "%@" causes a java.util.UnknownFormatConversionException. Fix: skip String.format when the args list is empty. All String.resolvableString usages pass emptyList() for args, so no formatting is ever needed — the value should be returned as-is. Adds regression tests in ResolvableStringTest covering format specifiers in no-arg static strings, and a test in CvcControllerTest confirming that contentDescription resolves without throwing when the CVC field contains "%@". Committed-By-Agent: goose * Address PR review feedback Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> --------- Co-authored-by: codex <noreply@openai.com>
1 parent 662c99b commit 9704a20

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

stripe-core/src/main/java/com/stripe/android/core/strings/StaticResolvableString.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ internal data class StaticResolvableString(
1111
) : ResolvableString {
1212
@Suppress("SpreadOperator")
1313
override fun resolve(context: Context): String {
14-
return value.format(*resolveArgs(context, args))
14+
val resolved = resolveArgs(context, args)
15+
// Avoid running String.format when there are no args: the value may be raw user input
16+
// containing percent signs (e.g. "%@", "50%"), which would cause a
17+
// java.util.UnknownFormatConversionException if passed through String.format as a
18+
// format string.
19+
return if (resolved.isEmpty()) value else value.format(*resolved)
1520
}
1621
}

stripe-core/src/test/java/com/stripe/android/core/strings/ResolvableStringTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.stripe.android.core.strings
22

33
import android.os.Bundle
44
import android.os.Parcel
5+
import androidx.test.core.app.ApplicationProvider
56
import androidx.test.runner.AndroidJUnit4
67
import com.stripe.android.core.strings.transformations.Replace
78
import org.junit.Test
@@ -102,6 +103,25 @@ class ResolvableStringTest {
102103
)
103104
}
104105

106+
@Test
107+
fun `static resolvable string with format specifier in value and no args resolves without error`() {
108+
val context = ApplicationProvider.getApplicationContext<android.content.Context>()
109+
// Values containing printf-style specifiers must not throw when there are no format args.
110+
assertEquals("%@", "%@".resolvableString.resolve(context))
111+
assertEquals("%s", "%s".resolvableString.resolve(context))
112+
assertEquals("%d", "%d".resolvableString.resolve(context))
113+
assertEquals("%@ 100 %s", "%@ 100 %s".resolvableString.resolve(context))
114+
assertEquals("100%", "100%".resolvableString.resolve(context))
115+
assertEquals("% discount", "% discount".resolvableString.resolve(context))
116+
}
117+
118+
@Test
119+
fun `static resolvable string with format specifier still formats when args are provided`() {
120+
val context = ApplicationProvider.getApplicationContext<android.content.Context>()
121+
assertEquals("hello world", resolvableString(value = "hello %s", "world").resolve(context))
122+
assertEquals("count: 42", resolvableString(value = "count: %d", 42).resolve(context))
123+
}
124+
105125
@Test
106126
fun `resolvable strings should parcelize and un-parcelize properly with raw value types`() {
107127
val identifierResolvable = IdentifierResolvableString(

0 commit comments

Comments
 (0)