This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-codec.git
The following commit(s) were added to refs/heads/master by this push:
new 94005258 Fix Base64.toIntegerBytes(BigInteger) for zero edge case.
(#441)
94005258 is described below
commit 9400525894adeb9a7edd5bd5ec4fe5eeb774d83b
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Jul 9 11:23:18 2026 -0400
Fix Base64.toIntegerBytes(BigInteger) for zero edge case. (#441)
---
.../org/apache/commons/codec/binary/Base64.java | 24 +-
.../codec/binary/Base64ToIntegerBytesTest.java | 284 +++++++++++++++++++++
2 files changed, 290 insertions(+), 18 deletions(-)
diff --git a/src/main/java/org/apache/commons/codec/binary/Base64.java
b/src/main/java/org/apache/commons/codec/binary/Base64.java
index a022ee65..2e899fa9 100644
--- a/src/main/java/org/apache/commons/codec/binary/Base64.java
+++ b/src/main/java/org/apache/commons/codec/binary/Base64.java
@@ -794,25 +794,13 @@ public class Base64 extends BaseNCodec {
* @return a byte array representation of the BigInteger parameter.
*/
static byte[] toIntegerBytes(final BigInteger bigInt) {
- int bitlen = bigInt.bitLength();
- // round bitlen
- bitlen = bitlen + 7 >> 3 << 3;
- final byte[] bigBytes = bigInt.toByteArray();
- if (bigInt.bitLength() % 8 != 0 && bigInt.bitLength() / 8 + 1 ==
bitlen / 8) {
- return bigBytes;
+ byte[] unsigned = bigInt.toByteArray();
+ if (unsigned[0] == 0 && unsigned.length != 1) {
+ final byte[] tmp = new byte[unsigned.length - 1];
+ System.arraycopy(unsigned, 1, tmp, 0, tmp.length);
+ unsigned = tmp;
}
- // set up params for copying everything but sign bit
- int startSrc = 0;
- int len = bigBytes.length;
- // if bigInt is exactly byte-aligned, just skip signbit in copy
- if (bigInt.bitLength() % 8 == 0) {
- startSrc = 1;
- len--;
- }
- final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
- final byte[] resizedBytes = new byte[bitlen / 8];
- System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
- return resizedBytes;
+ return unsigned;
}
static byte[] toUrlSafeEncodeTable(final boolean urlSafe) {
diff --git
a/src/test/java/org/apache/commons/codec/binary/Base64ToIntegerBytesTest.java
b/src/test/java/org/apache/commons/codec/binary/Base64ToIntegerBytesTest.java
new file mode 100644
index 00000000..68d3bd0d
--- /dev/null
+++
b/src/test/java/org/apache/commons/codec/binary/Base64ToIntegerBytesTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.codec.binary;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigInteger;
+
+import org.junit.jupiter.api.Test;
+
+class Base64ToIntegerBytesTest {
+
+ private static final byte[] ZERO_BYTE_ARRAY = new byte[] { 0 };
+
+ @Test
+ void testBigIntegerWithAllZeros() {
+ final BigInteger allZeros = new BigInteger("0");
+ final byte[] result = Base64.toIntegerBytes(allZeros);
+ assertArrayEquals(ZERO_BYTE_ARRAY, result);
+ }
+
+ @Test
+ void testBigIntegerWithBoundaryValues() {
+ // Test boundary values that might cause overflow or underflow issues
+ final BigInteger[] boundaries = {
BigInteger.valueOf(Integer.MAX_VALUE), BigInteger.valueOf(Integer.MIN_VALUE),
BigInteger.valueOf(Long.MAX_VALUE),
+ BigInteger.valueOf(Long.MIN_VALUE) };
+ for (final BigInteger boundary : boundaries) {
+ assertNotNull(Base64.toIntegerBytes(boundary), "Should handle
boundary value: " + boundary);
+ }
+ }
+
+ @Test
+ void testBigIntegerWithByteBoundaryValues() {
+ // Test specific byte boundary values
+ final BigInteger[] boundaryValues = {
BigInteger.valueOf(Byte.MAX_VALUE), // 127
+ BigInteger.valueOf(Byte.MIN_VALUE), // -128
+ BigInteger.valueOf(255), // 255 (unsigned byte max)
+ BigInteger.valueOf(-256) // -256
+ };
+ for (final BigInteger boundary : boundaryValues) {
+ assertNotNull(Base64.toIntegerBytes(boundary), "Should handle
boundary value: " + boundary);
+ }
+ }
+
+ @Test
+ void testBigIntegerWithDecimalString() {
+ final BigInteger decimal = new BigInteger("100");
+ final byte[] result = Base64.toIntegerBytes(decimal);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithHexValues() {
+ // Test with hex string representation
+ final BigInteger hexValue = new BigInteger("FF", 16); // 255
+ final byte[] result = Base64.toIntegerBytes(hexValue);
+ assertNotNull(result);
+ assertEquals(1, result.length);
+ assertEquals(-1, result[0]); // -1 in two's complement
+ }
+
+ @Test
+ void testBigIntegerWithLargeValues() {
+ // Test very large numbers that exceed typical integer ranges
+ final BigInteger huge = new BigInteger("9223372036854775807"); //
Long.MAX_VALUE
+ final byte[] result = Base64.toIntegerBytes(huge);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithLeadingZeros() {
+ // Create a BigInteger that would have leading zeros in byte
representation
+ final BigInteger big = new
BigInteger("00000000000000000000000000000001", 16);
+ final byte[] result = Base64.toIntegerBytes(big);
+ assertNotNull(result);
+ assertEquals(1, result.length);
+ assertEquals(1, result[0]);
+ }
+
+ @Test
+ void testBigIntegerWithMaxValue() {
+ final BigInteger max = BigInteger.valueOf(Long.MAX_VALUE);
+ final byte[] result = Base64.toIntegerBytes(max);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithMinValue() {
+ final BigInteger min = BigInteger.valueOf(Long.MIN_VALUE);
+ final byte[] result = Base64.toIntegerBytes(min);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithMixedSigns() {
+ // Test that the method correctly handles negative numbers
+ final BigInteger[] negativeValues = { BigInteger.valueOf(-1),
BigInteger.valueOf(-256), BigInteger.valueOf(Integer.MIN_VALUE),
+ new BigInteger("-999999999999999999") };
+ for (final BigInteger negative : negativeValues) {
+ assertNotNull(Base64.toIntegerBytes(negative), "Should handle
negative value: " + negative);
+ }
+ }
+
+ @Test
+ void testBigIntegerWithMultipleBytes() {
+ // Test a number that requires multiple bytes to represent
+ final BigInteger multiByte = new BigInteger("65536"); // 2^16
+ final byte[] result = Base64.toIntegerBytes(multiByte);
+ assertNotNull(result);
+ assertTrue(result.length >= 2);
+ // Should correctly represent the multi-byte value
+ }
+
+ @Test
+ void testBigIntegerWithNegativeHexValues() {
+ // Test with negative hex values
+ final BigInteger hexNeg = new BigInteger("80", 16); // -128 in two's
complement
+ final byte[] result = Base64.toIntegerBytes(hexNeg);
+ assertNotNull(result);
+ assertEquals(1, result.length);
+ assertEquals(-128, result[0]);
+ }
+
+ @Test
+ void testBigIntegerWithNegativeLargeValues() {
+ final BigInteger hugeNeg = new BigInteger("-9223372036854775808"); //
Long.MIN_VALUE
+ final byte[] result = Base64.toIntegerBytes(hugeNeg);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithNegativeVeryLargeNumber() {
+ final String largeString = "-1234567890123456789012345678901234567890";
+ final BigInteger large = new BigInteger(largeString);
+ final byte[] result = Base64.toIntegerBytes(large);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithOverflowProtection() {
+ // Test that the method handles overflow conditions gracefully
+ final BigInteger overflowTest = new
BigInteger("12345678901234567890123456789012345678901234567890");
+ final byte[] result = Base64.toIntegerBytes(overflowTest);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithSingleByteRange() {
+ // Test boundary values for single byte range
+ final BigInteger[] testValues = { BigInteger.valueOf(-128), // -128
(minimum byte value)
+ BigInteger.valueOf(-127), // -127
+ BigInteger.valueOf(-1), // -1
+ BigInteger.valueOf(0), // 0
+ BigInteger.valueOf(1), // 1
+ BigInteger.valueOf(126), // 126
+ BigInteger.valueOf(127) // 127 (maximum byte value)
+ };
+ for (final BigInteger value : testValues) {
+ final byte[] result = Base64.toIntegerBytes(value);
+ assertNotNull(result, "Should not return null for value: " +
value);
+ }
+ }
+
+ @Test
+ void testBigIntegerWithVeryLargeNumber() {
+ // Test with numbers that have many digits
+ final String largeString = "1234567890123456789012345678901234567890";
+ final BigInteger large = new BigInteger(largeString);
+ final byte[] result = Base64.toIntegerBytes(large);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithVeryLargePositiveValue() {
+ final BigInteger veryLarge = new
BigInteger("123456789012345678901234567890");
+ final byte[] result = Base64.toIntegerBytes(veryLarge);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithVerySmallNegativeValue() {
+ final BigInteger verySmall = new BigInteger("-256");
+ final byte[] result = Base64.toIntegerBytes(verySmall);
+ assertNotNull(result);
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testBigIntegerWithZeroInDifferentFormats() {
+ // Different ways to represent zero
+ final BigInteger zero1 = BigInteger.ZERO;
+ final BigInteger zero2 = new BigInteger("0");
+ final BigInteger zero3 = new BigInteger("0000000000000000");
+ final byte[] result1 = Base64.toIntegerBytes(zero1);
+ final byte[] result2 = Base64.toIntegerBytes(zero2);
+ final byte[] result3 = Base64.toIntegerBytes(zero3);
+ assertArrayEquals(ZERO_BYTE_ARRAY, result1);
+ assertArrayEquals(ZERO_BYTE_ARRAY, result2);
+ assertArrayEquals(ZERO_BYTE_ARRAY, result3);
+ }
+
+
+ @Test
+ void testLargeNegativeBigInteger() {
+ final BigInteger large = new BigInteger("-2147483648"); //
Integer.MIN_VALUE
+ final byte[] result = Base64.toIntegerBytes(large);
+ assertNotNull(result);
+ // Should handle large negative numbers correctly
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testLargePositiveBigInteger() {
+ final BigInteger large = new BigInteger("2147483647"); //
Integer.MAX_VALUE
+ final byte[] result = Base64.toIntegerBytes(large);
+ assertNotNull(result);
+ // Should handle large positive numbers correctly
+ assertTrue(result.length > 0);
+ }
+
+ @Test
+ void testNullBigInteger() {
+ assertThrows(NullPointerException.class, () -> {
+ Base64.toIntegerBytes(null);
+ });
+ }
+
+ @Test
+ void testOneBigInteger() {
+ final BigInteger one = BigInteger.ONE;
+ final byte[] result = Base64.toIntegerBytes(one);
+ assertNotNull(result);
+ // Should represent the integer value 1
+ assertEquals(1, result.length);
+ assertEquals(1, result[0]);
+ }
+
+ @Test
+ void testSmallNegativeBigInteger() {
+ final BigInteger small = new BigInteger("-128");
+ final byte[] result = Base64.toIntegerBytes(small);
+ assertNotNull(result);
+ assertEquals(1, result.length);
+ assertEquals(-128, result[0]);
+ }
+
+ @Test
+ void testSmallPositiveBigInteger() {
+ final BigInteger small = new BigInteger("127");
+ final byte[] result = Base64.toIntegerBytes(small);
+ assertNotNull(result);
+ assertEquals(1, result.length);
+ assertEquals(127, result[0]);
+ }
+
+}