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
commit 8c0303cece155da873ee15a4133dca00ddd1dd52 Author: Gary Gregory <[email protected]> AuthorDate: Fri Sep 18 05:48:16 2026 -0700 Fix Base58 encoded-length calculation and line-length validation Calculate the exact encoded length within the configured encode limit. Reject unsupported positive line lengths with IllegalArgumentException. Add regression tests and update documentation. --- src/changes/changes.xml | 1 + .../org/apache/commons/codec/binary/Base58.java | 38 +++++++++++++++++++++- .../apache/commons/codec/binary/Base58Test.java | 36 ++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8299dffd..25e3558f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -57,6 +57,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="fix" dev="ggregory" due-to="Gary Gregory">Base32 and Base64 STRICT decoding now require the encoder's canonical alphabet, padding, and line separators, and validate streams through EOF. Use LENIENT to retain permissive decoding.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Limit Base58 decoding to 8192 encoded bytes by default, checking cumulative stream input before buffering. Use Base58.Builder.setMaxDecodeLength(int) for larger trusted input. Encoding defaults to a configurable 8192-byte binary input limit through Base58.Builder.setMaxEncodeLength(int).</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Grow Base58 accumulation buffers geometrically within the configured input limits and validate actual accumulated length.</action> + <action type="fix" dev="ggregory" due-to="Gary Gregory">Implement Base58 encoded-length calculation and explicitly reject unsupported line chunking.</action> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add and use PhoneticEngine.Builder and deprecate old constructors.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add BeiderMorseEncoder.Builder and deprecate old constructor.</action> diff --git a/src/main/java/org/apache/commons/codec/binary/Base58.java b/src/main/java/org/apache/commons/codec/binary/Base58.java index e6b2cb0a..0076517e 100644 --- a/src/main/java/org/apache/commons/codec/binary/Base58.java +++ b/src/main/java/org/apache/commons/codec/binary/Base58.java @@ -104,6 +104,25 @@ public class Base58 extends BaseNCodec { return super.setEncodeTable(encodeTable); } + /** + * Sets the line length to zero. + * <p> + * Base58 does not support line chunking. Zero or a negative value selects unchunked output. + * </p> + * + * @param lineLength The line length; must not be positive. + * @return {@code this} instance. + * @throws IllegalArgumentException if lineLength is positive. + * @since 1.23.0 + */ + @Override + public Builder setLineLength(final int lineLength) { + if (lineLength > 0) { + throw new IllegalArgumentException("Base58 does not support line chunking."); + } + return super.setLineLength(lineLength); + } + /** * Sets the maximum number of encoded bytes accepted by a single decode operation. * <p> @@ -207,7 +226,7 @@ public class Base58 extends BaseNCodec { * * <pre> * Base58 base58 = Base58.builder() - * .setEncode(true) + * .setMaxEncodeLength(4096) * .get() * </pre> * @@ -458,6 +477,23 @@ public class Base58 extends BaseNCodec { code(array, offset, length, context, maxEncodeLength, "encode", this::convertToBase58); } + /** + * Gets the number of Base58 characters needed to encode the supplied array. + * <p> + * The length depends on the input bytes, including leading zeros. This method observes the configured maximum encode length. + * </p> + * + * @param array The binary input to encode. + * @return The number of Base58 characters that encoding the array produces. + * @throws IllegalArgumentException if the input exceeds the configured maximum encode length. + * @since 1.23.0 + */ + @Override + public long getEncodedLength(final byte[] array) { + checkLength(array.length, 0, maxEncodeLength, "encode"); + return getStringBuilder(array).length(); + } + /** * Builds the Base58 string representation of the given binary data. * <p> diff --git a/src/test/java/org/apache/commons/codec/binary/Base58Test.java b/src/test/java/org/apache/commons/codec/binary/Base58Test.java index 5d7b3f6c..5ae27d94 100644 --- a/src/test/java/org/apache/commons/codec/binary/Base58Test.java +++ b/src/test/java/org/apache/commons/codec/binary/Base58Test.java @@ -204,6 +204,31 @@ public class Base58Test { } } + @Test + void testEncodedLength() { + final Base58 codec = new Base58(); + assertEquals(0, codec.getEncodedLength(new byte[0])); + assertEquals(1, codec.getEncodedLength(new byte[] { 0 })); + assertEquals(3, codec.getEncodedLength(new byte[] { 0, 0, 0 })); + assertEquals(1, codec.getEncodedLength(new byte[] { 57 })); + assertEquals(2, codec.getEncodedLength(new byte[] { 58 })); + assertEquals(3, codec.getEncodedLength(new byte[] { 0, 58 })); + final Random random = new Random(58); + for (int length = 1; length <= 256; length++) { + final byte[] input = new byte[length]; + random.nextBytes(input); + assertEquals(codec.encode(input).length, codec.getEncodedLength(input)); + } + } + + @Test + void testEncodedLengthLimit() { + final Base58 codec = Base58.builder().setMaxEncodeLength(10).get(); + assertEquals(10, codec.getEncodedLength(new byte[10])); + assertThrows(IllegalArgumentException.class, () -> codec.getEncodedLength(new byte[11])); + assertEquals(11, Base58.builder().setMaxEncodeLength(11).get().getEncodedLength(new byte[11])); + } + @Test void testHexEncoding() { final String hexString = "48656c6c6f20576f726c6421"; @@ -330,6 +355,17 @@ public class Base58Test { assertArrayEquals(input, decoded, "Decoded should match original including leading zeros"); } + @Test + void testLineLength() { + assertThrows(IllegalArgumentException.class, () -> Base58.builder().setLineLength(76)); + assertThrows(IllegalArgumentException.class, () -> Base58.builder().setLineSeparator(new byte[0]).setLineLength(1)); + for (final int length : new int[] { 0, -1 }) { + final Base58 codec = Base58.builder().setLineLength(length).get(); + assertArrayEquals(new byte[] { '2', '1' }, codec.encode(new byte[] { 58 })); + assertEquals(2, codec.getEncodedLength(new byte[] { 58 })); + } + } + @Test void testObjectDecodeWithInvalidParameter() { assertThrows(DecoderException.class, () -> new Base58().decode(Integer.valueOf(5)));
