You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by re...@apache.org on 2026/04/13 14:54:50 UTC
(jackrabbit) branch 2.22 updated: JCR-5226: Base64 decoder does not handle lack of padding correctly (#332)
This is an automated email from the ASF dual-hosted git repository.
reschke pushed a commit to branch 2.22
in repository https://gitbox.apache.org/repos/asf/jackrabbit.git
The following commit(s) were added to refs/heads/2.22 by this push:
new aef48094b JCR-5226: Base64 decoder does not handle lack of padding correctly (#332)
aef48094b is described below
commit aef48094b8b730467020ca6a0c23061eaf4b6990
Author: Julian Reschke <re...@apache.org>
AuthorDate: Mon Feb 9 18:03:36 2026 +0100
JCR-5226: Base64 decoder does not handle lack of padding correctly (#332)
* JCR-5226: Base64 decoder does not handle input without padding correctly - failing test case
* JCR-5226: Base64 decoder does not handle input without padding correctly - minimal change to handle missing padding (instead of returning garbage)
* JCR-5226: more test coverage
* JCR-5226: fix one more case and add more test coverage
* JCR-5226: clean up test class
* JCR-5226: minor refactoring of base class
* JCR-5226: javadoc
---
.../java/org/apache/jackrabbit/util/Base64.java | 57 ++++++++++++++++---
.../org/apache/jackrabbit/util/Base64Test.java | 65 ++++++++++++++++++----
2 files changed, 105 insertions(+), 17 deletions(-)
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
index 6a8557ee7..ae2925f57 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
@@ -32,9 +32,12 @@ import java.util.Arrays;
/**
* <code>Base64</code> provides Base64 encoding/decoding of strings and streams.
* <p>
+ * <em>NOTE:</em> the decoder accepts invalid input (such as non-trailing padding characters)
+ * and just returns broken output (see JCR-5227).
+ * <p>
* See <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-4">RFC 4648, Section 4</a>.
* <p>
- * See {@link java.util.Base64} for a JDK alternative.
+ * See {@link java.util.Base64} for a better JDK alternative.
*/
public class Base64 {
@@ -323,6 +326,20 @@ public class Base64 {
decode(chars, 0, chars.length, out);
}
+ // utility methods to decode into 1st, 2nd and 3rd position of output
+
+ private static byte decodeFirst(int b0, int b1) {
+ return (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ }
+
+ private static byte decodeSecond(int b1, int b2) {
+ return (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ }
+
+ private static byte decodeThird(int b2, int b3) {
+ return (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
+ }
+
/**
* Decode base64 encoded data.
*
@@ -356,16 +373,16 @@ public class Base64 {
int b2 = DECODETABLE[chunk[2]];
int b3 = DECODETABLE[chunk[3]];
if (chunk[3] == BASE64PAD && chunk[2] == BASE64PAD) {
- dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+ dec[0] = decodeFirst(b0, b1);
out.write(dec, 0, 1);
} else if (chunk[3] == BASE64PAD) {
- dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
- dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+ dec[0] = decodeFirst(b0, b1);
+ dec[1] = decodeSecond(b1, b2);
out.write(dec, 0, 2);
} else {
- dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
- dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
- dec[2] = (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
+ dec[0] = decodeFirst(b0, b1);
+ dec[1] = decodeSecond(b1, b2);
+ dec[2] = decodeThird(b2, b3);
out.write(dec, 0, 3);
}
posChunk = 0;
@@ -374,5 +391,31 @@ public class Base64 {
throw new IllegalArgumentException("specified data is not base64 encoded");
}
}
+
+ // if there is an incomplete chunk...
+ if (posChunk != 0) {
+ boolean lastCharWasPad = chunk[posChunk - 1] == BASE64PAD;
+ if (lastCharWasPad) {
+ throw new IllegalArgumentException("specified data is not base64 encoded (input ends with unexpected pad character");
+ }
+
+ // handle missing padding gracefully, inspired by
+ // https://datatracker.ietf.org/doc/html/rfc7515#appendix-C
+
+ if (posChunk == 1) {
+ throw new IllegalArgumentException("specified data is not base64 encoded (extra non-pad character");
+ }
+
+ if (posChunk == 2) {
+ // no padding, input length == 2; add two pad characters
+ chunk[2] = BASE64PAD;
+ chunk[3] = BASE64PAD;
+ } else {
+ // no padding, input length == 3: add one pad character
+ chunk[3] = BASE64PAD;
+ }
+
+ decode(chunk, out);
+ }
}
}
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/Base64Test.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/Base64Test.java
index ac1b461d8..c79ef15d9 100644
--- a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/Base64Test.java
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/util/Base64Test.java
@@ -18,19 +18,20 @@ package org.apache.jackrabbit.util;
import junit.framework.TestCase;
-import java.io.IOException;
import java.io.StringWriter;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Random;
-import java.util.Arrays;
+
+import static org.junit.Assert.assertArrayEquals;
/**
* Test cases for Base64 encode / decode.
*/
public class Base64Test extends TestCase {
- private Random _random = new Random();
+ private final Random _random = new Random();
/**
* @return Returns the _random.
@@ -45,8 +46,7 @@ public class Base64Test extends TestCase {
public void testWhitespace() throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
Base64.decode(" d G\tV \tzdA\n= =\n", buffer);
- byte[] data = buffer.toByteArray();
- assertEquals("test", new String(data, "US-ASCII"));
+ assertEquals("test", buffer.toString(StandardCharsets.US_ASCII));
}
/**
@@ -71,7 +71,7 @@ public class Base64Test extends TestCase {
baos = new ByteArrayOutputStream();
Base64.decode(new ByteArrayInputStream(encData), baos);
byte[] decData = baos.toByteArray();
- assertTrue(Arrays.equals(data, decData));
+ assertArrayEquals(data, decData);
}
private void base64RoundTrip() throws Exception {
@@ -98,26 +98,71 @@ public class Base64Test extends TestCase {
}
}
- public void testDecodeOrEncode() throws IOException {
+ public void testDecodeOrEncode() {
assertEquals("", Base64.decodeOrEncode(Base64.decodeOrEncode("")));
assertEquals("test", Base64.decodeOrEncode(Base64.decodeOrEncode("test")));
assertEquals("{base64}dGVzdA==", Base64.decodeOrEncode("test"));
assertEquals("test", Base64.decodeOrEncode("{base64}dGVzdA=="));
}
- public void testDecodeIfEncoded() throws IOException {
- assertEquals(null, Base64.decodeIfEncoded(null));
+ public void testDecodeIfEncoded() {
+ assertNull(Base64.decodeIfEncoded(null));
assertEquals("", Base64.decodeIfEncoded(""));
assertEquals("", Base64.decodeIfEncoded("{base64}"));
assertEquals("test", Base64.decodeIfEncoded("test"));
assertEquals("test", Base64.decodeIfEncoded("{base64}dGVzdA=="));
}
- public void testStringEncodeDecode() throws IOException {
+ public void testStringEncodeDecode() {
assertEquals("", Base64.decode(Base64.encode("")));
assertEquals("test", Base64.decode(Base64.encode("test")));
assertEquals("dGVzdA==", Base64.encode("test"));
assertEquals("test", Base64.decode("dGVzdA=="));
}
+ public void testStringDecodeNoPadding() {
+ // no padding variants tested in comparison to padded ones
+ // https://datatracker.ietf.org/doc/html/rfc4648#section-10
+
+ assertEquals("foob", Base64.decode("Zm9vYg=="));
+ assertEquals("foob", Base64.decode("Zm9vYg"));
+
+ assertEquals("fooba", Base64.decode("Zm9vYmE="));
+ assertEquals("fooba", Base64.decode("Zm9vYmE"));
+ }
+
+ public void testStringDecodeBrokenPadding() {
+ // incorrect number of padding characters (when padding, the total
+ // length needs to be a multiple of 4)
+
+ // confirming with jdk decoder
+ try {
+ java.util.Base64.getDecoder().decode("Zm9vYg");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Jackrabbit API is weird; for broken input, it just returns
+ // the input
+ assertEquals("Zm9vYg=", Base64.decode("Zm9vYg="));
+ }
+
+ public void testStringIllegalExtraChar() {
+ // last (4 byte) chunk of length 1
+ // test with pad and non-pad
+
+ // confirming with jdk decoder
+ try {
+ java.util.Base64.getDecoder().decode("extra");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ java.util.Base64.getDecoder().decode("extr=");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Jackrabbit API is weird; for broken input, it just returns
+ // the input
+ assertEquals("extra", Base64.decode("extra"));
+ assertEquals("extr=", Base64.decode("extr="));
+ }
}
\ No newline at end of file