Skip to content

Commit 1b24ad3

Browse files
committed
fix: preserve base64 invalid-character errors
Motivation: Non-ASCII base64 inputs can have lengths that are not multiples of four, but they should fail as invalid characters rather than padding-length errors. Modification: Factor the strict ASCII padding predicate into Base64Validation, reuse it in decode catch paths, and add direct high-codepoint decode regression tests while restoring golden output. Result: std.base64Decode and std.base64DecodeBytes report the correct failure class for high-codepoint inputs without changing ASCII padding validation.
1 parent 2085c26 commit 1b24ad3

5 files changed

Lines changed: 32 additions & 17 deletions

File tree

sjsonnet/src/sjsonnet/stdlib/Base64Validation.scala

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ package sjsonnet.stdlib
66
*/
77
object Base64Validation {
88

9+
def hasStrictPaddingLengthError(input: String): Boolean = {
10+
val len = input.length
11+
if (len == 0 || len % 4 == 0) false
12+
else {
13+
var allAscii = true
14+
var i = 0
15+
while (i < len && allAscii) {
16+
if (input.charAt(i) > 127) allAscii = false
17+
i += 1
18+
}
19+
allAscii
20+
}
21+
}
22+
923
/**
1024
* Validate strict RFC 4648 padding: reject ASCII input whose length is not a multiple of 4.
1125
*
@@ -20,19 +34,10 @@ object Base64Validation {
2034
* bytes (so "ĀQ=" is 4 bytes, passes the length check, and fails on the invalid character).
2135
*/
2236
def requireStrictPadding(input: String): Unit = {
23-
val len = input.length
24-
if (len > 0 && len % 4 != 0) {
25-
var allAscii = true
26-
var i = 0
27-
while (i < len && allAscii) {
28-
if (input.charAt(i) > 127) allAscii = false
29-
i += 1
30-
}
31-
if (allAscii) {
32-
throw new IllegalArgumentException(
33-
"Last unit does not have enough valid bits"
34-
)
35-
}
37+
if (hasStrictPaddingLengthError(input)) {
38+
throw new IllegalArgumentException(
39+
"Last unit does not have enough valid bits"
40+
)
3641
}
3742
}
3843
}

sjsonnet/src/sjsonnet/stdlib/EncodingModule.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ object EncodingModule extends AbstractFunctionModule {
118118
else Val.Str(pos, value)): Val
119119
} catch {
120120
case e: IllegalArgumentException =>
121-
if (str.length % 4 != 0)
121+
if (Base64Validation.hasStrictPaddingLengthError(str))
122122
Error.fail(
123123
s"input string appears not to be a base64 encoded string. Wrong length found (${str.length})"
124124
)
@@ -140,7 +140,7 @@ object EncodingModule extends AbstractFunctionModule {
140140
Val.Arr.fromBytes(pos, PlatformBase64.decode(str))
141141
} catch {
142142
case e: IllegalArgumentException =>
143-
if (str.length % 4 != 0)
143+
if (Base64Validation.hasStrictPaddingLengthError(str))
144144
Error.fail(
145145
s"input string appears not to be a base64 encoded string. Wrong length found (${str.length})"
146146
)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
sjsonnet.Error: [std.base64DecodeBytes] input string appears not to be a base64 encoded string. Wrong length found (3)
1+
sjsonnet.Error: [std.base64DecodeBytes] failed to decode: Illegal base64 character 3f
22
at [<root>].(builtinBase64DecodeBytes_high_codepoint.jsonnet:1:22)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
sjsonnet.Error: [std.base64Decode] input string appears not to be a base64 encoded string. Wrong length found (3)
1+
sjsonnet.Error: [std.base64Decode] failed to decode: Illegal base64 character 3f
22
at [<root>].(builtinBase64Decode_high_codepoint.jsonnet:1:17)

sjsonnet/test/src/sjsonnet/Base64Tests.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,16 @@ object Base64Tests extends TestSuite {
465465
val err = evalErr("""std.base64Decode("!!!!")""")
466466
assert(err.contains("failed to decode"))
467467
}
468+
test("highCodepointInvalidChar") {
469+
val err = evalErr("""std.base64Decode("\u0100Q=")""")
470+
assert(err.contains("failed to decode"))
471+
assert(!err.contains("Wrong length found"))
472+
}
473+
test("decodeBytesHighCodepointInvalidChar") {
474+
val err = evalErr("""std.base64DecodeBytes("\u0100Q=")""")
475+
assert(err.contains("failed to decode"))
476+
assert(!err.contains("Wrong length found"))
477+
}
468478
test("invalidByteArrayValue") {
469479
val err = evalErr("""std.base64([256])""")
470480
assert(err.contains("invalid codepoint"))

0 commit comments

Comments
 (0)