Skip to content

Commit c019e9e

Browse files
fix(compiler): harden HoloQR payload admission
PASS: pnpm check:holoqr-born-from-source PASS: pnpm holoqr:build-release PASS: QrPayloadFactsTest (2 tests, 0 failures) PASS: apksigner verify --verbose
1 parent dbc223d commit c019e9e

5 files changed

Lines changed: 22 additions & 13 deletions

File tree

apps/quest-universal-qr-scanner/android-mr/app/src/main/java/net/holoscript/qrscanner/StarterSampleActivity.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal object QrPayloadFacts {
6565
return lines.size >= 3 &&
6666
lines.first().trim().equals(begin, ignoreCase = true) &&
6767
lines.last().trim().equals(end, ignoreCase = true) &&
68-
lines.subList(1, lines.lastIndex).any { line ->
68+
lines.subList(1, lines.lastIndex).all { line ->
6969
val separator = line.indexOf(':')
7070
separator > 0 && separator < line.lastIndex
7171
}
@@ -93,10 +93,10 @@ internal object QrPayloadFacts {
9393
if (trimmed.startsWith("BEGIN:VEVENT", ignoreCase = true)) {
9494
return validStructuredEnvelope(trimmed, "BEGIN:VEVENT", "END:VEVENT")
9595
}
96-
val declaresWebUrl =
97-
trimmed.startsWith("http://", ignoreCase = true) ||
98-
trimmed.startsWith("https://", ignoreCase = true)
99-
if (!declaresWebUrl) return true
96+
val declaresWebScheme =
97+
trimmed.startsWith("http:", ignoreCase = true) ||
98+
trimmed.startsWith("https:", ignoreCase = true)
99+
if (!declaresWebScheme) return true
100100
val uri = try { URI(trimmed).parseServerAuthority() } catch (_: Exception) { return false }
101101
val validPort = uri.port == -1 || uri.port in 1..65535
102102
return (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) &&

apps/quest-universal-qr-scanner/android-mr/app/src/test/java/net/holoscript/qrscanner/QrPayloadFactsTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class QrPayloadFactsTest {
88
@Test
99
fun rejectsMalformedOrAmbiguousWebAuthorities() {
1010
assertFalse(QrPayloadFacts.syntaxSafe("https://"))
11+
assertFalse(QrPayloadFacts.syntaxSafe("https:/example.com"))
1112
assertFalse(QrPayloadFacts.syntaxSafe("https://example.com:bad"))
1213
assertFalse(QrPayloadFacts.syntaxSafe("https://user@example.com/path"))
1314
assertFalse(QrPayloadFacts.syntaxSafe("https://example.com:70000/path"))
@@ -19,6 +20,7 @@ class QrPayloadFactsTest {
1920
assertFalse(QrPayloadFacts.controlsSafe("https://example.com/\nextra"))
2021
assertFalse(QrPayloadFacts.controlsSafe("plain\ttext"))
2122
assertFalse(QrPayloadFacts.syntaxSafe("BEGIN:VCARD\nnot-a-card"))
23+
assertFalse(QrPayloadFacts.syntaxSafe("BEGIN:VCARD\nFN:Ada\ngarbage\nEND:VCARD"))
2224
assertTrue(QrPayloadFacts.controlsSafe("BEGIN:VCARD\r\nFN:Ada\r\nEND:VCARD"))
2325
assertTrue(QrPayloadFacts.syntaxSafe("BEGIN:VCARD\r\nFN:Ada\r\nEND:VCARD"))
2426
assertTrue(QrPayloadFacts.controlsSafe("BEGIN:VEVENT\nSUMMARY:Launch\nEND:VEVENT"))

apps/quest-universal-qr-scanner/scripts/build-release.mjs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,15 @@ export async function main(argv = process.argv.slice(2)) {
140140
// Windows batch files must run through cmd.exe. Pass the absolute path and avoid shell:true.
141141
const cmd = isWin ? 'cmd.exe' : gradlewPath;
142142
const args = isWin
143-
? ['/c', gradlewPath, ':app:assembleRelease', '--console=plain', '--no-daemon']
144-
: [':app:assembleRelease', '--console=plain', '--no-daemon'];
143+
? [
144+
'/c',
145+
gradlewPath,
146+
':app:testDebugUnitTest',
147+
':app:assembleRelease',
148+
'--console=plain',
149+
'--no-daemon',
150+
]
151+
: [':app:testDebugUnitTest', ':app:assembleRelease', '--console=plain', '--no-daemon'];
145152

146153
try {
147154
const result = spawnSync(cmd, args, {

packages/core/src/compiler/quest-mr-templates.generated.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/core/src/compiler/quest-mr-templates/StarterSampleActivity.kt.tmpl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal object QrPayloadFacts {
6565
return lines.size >= 3 &&
6666
lines.first().trim().equals(begin, ignoreCase = true) &&
6767
lines.last().trim().equals(end, ignoreCase = true) &&
68-
lines.subList(1, lines.lastIndex).any { line ->
68+
lines.subList(1, lines.lastIndex).all { line ->
6969
val separator = line.indexOf(':')
7070
separator > 0 && separator < line.lastIndex
7171
}
@@ -93,10 +93,10 @@ internal object QrPayloadFacts {
9393
if (trimmed.startsWith("BEGIN:VEVENT", ignoreCase = true)) {
9494
return validStructuredEnvelope(trimmed, "BEGIN:VEVENT", "END:VEVENT")
9595
}
96-
val declaresWebUrl =
97-
trimmed.startsWith("http://", ignoreCase = true) ||
98-
trimmed.startsWith("https://", ignoreCase = true)
99-
if (!declaresWebUrl) return true
96+
val declaresWebScheme =
97+
trimmed.startsWith("http:", ignoreCase = true) ||
98+
trimmed.startsWith("https:", ignoreCase = true)
99+
if (!declaresWebScheme) return true
100100
val uri = try { URI(trimmed).parseServerAuthority() } catch (_: Exception) { return false }
101101
val validPort = uri.port == -1 || uri.port in 1..65535
102102
return (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) &&

0 commit comments

Comments
 (0)