ReviewOS

also looking at this

craft-native/craft

fix(android): jsQuote takes Any?, because three call sites are not strings

#164
Merged glennmichael123 wants to merge fix/android-jsquote-types into main
2 files +18 -5
packages/android/templates/CraftBridge.kt.templatemodified+9-4
Changes to packages/android/templates/CraftBridge.kt.template
@@ -170,15 +170,20 @@ class CraftBridge(
170170 * Quote a value for interpolation into JavaScript.
171171 *
172172 * The result carries its own quotes, so write `f($payload)` — leaving the
173 * surrounding `'...'` in place would quote it twice. A Kotlin null renders as
174 * the string "null", which is what plain interpolation produced before; whether
175 * that should instead be a JS null is a separate question from escaping.
173 * surrounding `'...'` in place would quote it twice.
176174 *
177175 * Without this, a value containing an apostrophe ends the JS string early, the
178176 * script fails to parse, and `evaluateJavascript` runs nothing at all — so a
179177 * hand-built promise with no timeout never settles.
178 *
179 * `Any?` rather than `String?`, because these call sites replaced string
180 * interpolation and interpolation accepts anything: `contactId` is the `Long`
181 * from `ContentUris.parseId`, `downloadId` the `Long` from
182 * `DownloadManager.enqueue`, and `errString` the `CharSequence` a
183 * `BiometricPrompt` error arrives as. `Any?.toString()` is exactly what `$x`
184 * did, null included — it renders as the string "null", the way it always has.
180185 */
181 private fun jsQuote(value: String?): String = JSONObject.quote(value ?: "null")
186 private fun jsQuote(value: Any?): String = JSONObject.quote(value.toString())
182187
183188 fun injectBridge() {
184189 // Before the script, so a native callback arriving while the page is
packages/zig/test/android_reply_escaping_test.zigmodified+9-1
Changes to packages/zig/test/android_reply_escaping_test.zig
@@ -143,6 +143,14 @@ test "the templates that answer the page are actually in the scanned set" {
143143
144144 // And the fix is present rather than the sites merely being deleted.
145145 const bridge = sourceOf("CraftBridge.kt.template").?;
146 try testing.expect(std.mem.indexOf(u8, bridge, "private fun jsQuote(") != null);
147146 try testing.expect(std.mem.count(u8, bridge, "jsQuote(") >= 50);
147
148 // `Any?`, not `String?`. The call sites replaced string interpolation,
149 // which accepts anything — `contactId` is a Long, `downloadId` is a Long,
150 // `errString` is a CharSequence — and narrowing the parameter back makes
151 // those three sites stop compiling. Nothing in CI compiles Kotlin, so this
152 // is the only thing that would say so. See #163.
153 try testing.expect(
154 std.mem.indexOf(u8, bridge, "private fun jsQuote(value: Any?): String") != null,
155 );
148156}