This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 7.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit cefae342c15b18868dbbd24a17bb91fe0c37b5db Author: Mark Thomas <ma...@apache.org> AuthorDate: Fri Dec 6 14:29:01 2019 +0000 Merge in Codec changes to 9637dd4 (2019-12-06, 1.14-SNAPSHOT) --- .../apache/tomcat/util/codec/binary/Base64.java | 28 ++++-- .../tomcat/util/codec/binary/BaseNCodec.java | 112 ++++++++++++++++++--- webapps/docs/changelog.xml | 4 + 3 files changed, 119 insertions(+), 25 deletions(-) diff --git a/java/org/apache/tomcat/util/codec/binary/Base64.java b/java/org/apache/tomcat/util/codec/binary/Base64.java index 3d40748..ce6311c 100644 --- a/java/org/apache/tomcat/util/codec/binary/Base64.java +++ b/java/org/apache/tomcat/util/codec/binary/Base64.java @@ -126,6 +126,10 @@ public class Base64 extends BaseNCodec { */ /** Mask used to extract 6 bits, used when encoding */ private static final int MASK_6BITS = 0x3f; + /** Mask used to extract 4 bits, used when decoding final trailing character. */ + private static final int MASK_4BITS = 0xf; + /** Mask used to extract 2 bits, used when decoding final trailing character. */ + private static final int MASK_2BITS = 0x3; // The static final fields above are used for the original static byte[] methods on Base64. // The private member fields below are used with the new streaming approach, which requires @@ -468,12 +472,12 @@ public class Base64 extends BaseNCodec { // TODO not currently tested; perhaps it is impossible? break; case 2 : // 12 bits = 8 + 4 - validateCharacter(4, context); + validateCharacter(MASK_4BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); break; case 3 : // 18 bits = 8 + 8 + 2 - validateCharacter(2, context); + validateCharacter(MASK_2BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); @@ -789,20 +793,22 @@ public class Base64 extends BaseNCodec { /** - * <p> - * Validates whether the character is possible in the context of the set of possible base 64 values. - * </p> + * Validates whether decoding the final trailing character is possible in the context + * of the set of possible base 64 values. + * + * <p>The character is valid if the lower bits within the provided mask are zero. This + * is used to test the final trailing base-64 digit is zero in the bits that will be discarded. * - * @param numBitsToDrop number of least significant bits to check + * @param emptyBitsMask The mask of the lower bits that should be empty * @param context the context to be used * * @throws IllegalArgumentException if the bits being checked contain any non-zero value */ - private long validateCharacter(final int numBitsToDrop, final Context context) { - if ((context.ibitWorkArea & numBitsToDrop) != 0) { - throw new IllegalArgumentException( - "Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value"); + private static void validateCharacter(final int emptyBitsMask, final Context context) { + if ((context.ibitWorkArea & emptyBitsMask) != 0) { + throw new IllegalArgumentException( + "Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value. " + + "Expected the discarded bits to be zero."); } - return context.ibitWorkArea >> numBitsToDrop; } } diff --git a/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java index ecc761d..0245942 100644 --- a/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java +++ b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java @@ -143,6 +143,18 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { */ private static final int DEFAULT_BUFFER_SIZE = 128; + /** + * The maximum size buffer to allocate. + * + * <p>This is set to the same size used in the JDK {@code java.util.ArrayList}:</p> + * <blockquote> + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit. + * </blockquote> + */ + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + /** Mask used to extract 8 bits, used in decoding bytes */ protected static final int MASK_8BITS = 0xff; @@ -172,7 +184,7 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { private final int chunkSeparatorLength; /** - * Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize} + * Note <code>lineLength</code> is rounded down to the nearest multiple of the encoded block size. * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled. * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) @@ -181,6 +193,20 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { */ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, final int lineLength, final int chunkSeparatorLength) { + this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); + } + + /** + * Note <code>lineLength</code> is rounded down to the nearest multiple of the encoded block size. + * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled. + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length <code>lineLength</code> + * @param chunkSeparatorLength the chunk separator length, if relevant + * @param pad byte used as padding byte. + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength, final byte pad) { this.unencodedBlockSize = unencodedBlockSize; this.encodedBlockSize = encodedBlockSize; final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; @@ -211,7 +237,7 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { /** * Get the default buffer size. Can be overridden. * - * @return {@link #DEFAULT_BUFFER_SIZE} + * @return the default buffer size. */ protected int getDefaultBufferSize() { return DEFAULT_BUFFER_SIZE; @@ -220,18 +246,69 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { /** * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. * @param context the context to be used + * @param minCapacity the minimum required capacity + * @return the resized byte[] buffer + * @throws OutOfMemoryError if the {@code minCapacity} is negative */ - private byte[] resizeBuffer(final Context context) { - if (context.buffer == null) { - context.buffer = new byte[getDefaultBufferSize()]; - context.pos = 0; - context.readPos = 0; - } else { - final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); - context.buffer = b; + private static byte[] resizeBuffer(final Context context, final int minCapacity) { + // Overflow-conscious code treats the min and new capacity as unsigned. + final int oldCapacity = context.buffer.length; + int newCapacity = oldCapacity * DEFAULT_BUFFER_RESIZE_FACTOR; + if (compareUnsigned(newCapacity, minCapacity) < 0) { + newCapacity = minCapacity; } - return context.buffer; + if (compareUnsigned(newCapacity, MAX_BUFFER_SIZE) > 0) { + newCapacity = createPositiveCapacity(minCapacity); + } + + final byte[] b = new byte[newCapacity]; + System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); + context.buffer = b; + return b; + } + + /** + * Compares two {@code int} values numerically treating the values + * as unsigned. Taken from JDK 1.8. + * + * <p>TODO: Replace with JDK 1.8 Integer::compareUnsigned(int, int).</p> + * + * @param x the first {@code int} to compare + * @param y the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; a value less + * than {@code 0} if {@code x < y} as unsigned values; and + * a value greater than {@code 0} if {@code x > y} as + * unsigned values + */ + private static int compareUnsigned(int x, int y) { + return Integer.valueOf(x + Integer.MIN_VALUE).compareTo(Integer.valueOf(y + Integer.MIN_VALUE)); + } + + /** + * Create a positive capacity at least as large the minimum required capacity. + * If the minimum capacity is negative then this throws an OutOfMemoryError as no array + * can be allocated. + * + * @param minCapacity the minimum capacity + * @return the capacity + * @throws OutOfMemoryError if the {@code minCapacity} is negative + */ + private static int createPositiveCapacity(int minCapacity) { + if (minCapacity < 0) { + // overflow + throw new OutOfMemoryError("Unable to allocate array size: " + (minCapacity & 0xffffffffL)); + } + // This is called when we require buffer expansion to a very big array. + // Use the conservative maximum buffer size if possible, otherwise the biggest required. + // + // Note: In this situation JDK 1.8 java.util.ArrayList returns Integer.MAX_VALUE. + // This excludes some VMs that can exceed MAX_BUFFER_SIZE but not allocate a full + // Integer.MAX_VALUE length array. + // The result is that we may have to allocate an array of this size more than once if + // the capacity must be expanded again. + return (minCapacity > MAX_BUFFER_SIZE) ? + minCapacity : + MAX_BUFFER_SIZE; } /** @@ -242,8 +319,15 @@ public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { * @return the buffer */ protected byte[] ensureBufferSize(final int size, final Context context){ - if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ - return resizeBuffer(context); + if (context.buffer == null) { + context.buffer = new byte[getDefaultBufferSize()]; + context.pos = 0; + context.readPos = 0; + + // Overflow-conscious: + // x + y > z == x + y - z > 0 + } else if (context.pos + size - context.buffer.length > 0) { + return resizeBuffer(context, context.pos + size); } return context.buffer; } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 2e9cce6..1dad7fe 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -161,6 +161,10 @@ Update the internal fork of Apache Commons BCEL to ff6941e (2019-12-06, 6.4.2-dev). Code clean-up only. (markt) </add> + <add> + Update the internal fork of Apache Commons Codec to 9637dd4 (2019-12-06, + 1.14-SNAPSHOT). Code clean-up and a fix for CODEC-265. (markt) + </add> </changelog> </subsection> </section> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org