You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by jo...@apache.org on 2008/06/18 21:16:04 UTC

svn commit: r669256 - in /commons/proper/codec/trunk/src: java/org/apache/commons/codec/binary/ test/org/apache/commons/codec/binary/

Author: jochen
Date: Wed Jun 18 12:16:03 2008
New Revision: 669256

URL: http://svn.apache.org/viewvc?rev=669256&view=rev
Log:
PR: CODED-69
Submitted-By: Julius Davies <ju...@gmail.com>
Added the Base64InputStream and the Base64OutputStream.

Added:
    commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64InputStream.java
    commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64OutputStream.java
    commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java
    commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64OutputStreamTest.java
    commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64TestData.java
Modified:
    commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java

Modified: commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java?rev=669256&r1=669255&r2=669256&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java (original)
+++ commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java Wed Jun 18 12:16:03 2008
@@ -22,6 +22,7 @@
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.codec.EncoderException;
 
+import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 
 /**
@@ -38,7 +39,6 @@
  * @version $Id$
  */
 public class Base64 implements BinaryEncoder, BinaryDecoder {
-
     /**
      * Chunk size per RFC 2045 section 6.8.
      * 
@@ -59,104 +59,392 @@
     static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
 
     /**
-     * The base length.
+     * Byte used to pad output.
      */
-    private static final int BASELENGTH = 255;
+    private static final byte PAD = (byte) '=';
+
+
+    // 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
+    // some state be preserved between calls of encode() and decode().
+
 
     /**
-     * Lookup length.
+     * Line length for encoding.  Not used when decoding.  Any value of zero or less implies
+     * so chunking of the base64 encoded data.
      */
-    private static final int LOOKUPLENGTH = 64;
+    private final int lineLength;
 
     /**
-     * Used to calculate the number of bits in a byte.
+     * Line separator for encoding.  Not used when decoding.  Only used if lineLength >= 1.
      */
-    private static final int EIGHTBIT = 8;
+    private final byte[] lineSeparator;
 
     /**
-     * Used when encoding something which has fewer than 24 bits.
+     * Convenience variable to help us determine when our buffer is going to run out of
+     * room and needs resizing.  <code>decodeSize = 3 + lineSeparator.length;</code>
      */
-    private static final int SIXTEENBIT = 16;
+    private final int decodeSize;
 
     /**
-     * Used to determine how many bits data contains.
+     * Convenience variable to help us determine when our buffer is going to run out of
+     * room and needs resizing.  <code>encodeSize = 4 + lineSeparator.length;</code>
      */
-    private static final int TWENTYFOURBITGROUP = 24;
+    private final int encodeSize;
 
     /**
-     * Used to get the number of Quadruples.
+     * Buffer for streaming. 
      */
-    private static final int FOURBYTE = 4;
+    private byte[] buf;
 
     /**
-     * Used to test the sign of a byte.
+     * Position where next character should be written in the buffer.
      */
-    private static final int SIGN = -128;
+    private int pos;
 
     /**
-     * Byte used to pad output.
+     * Position where next character should be read from the buffer.
      */
-    private static final byte PAD = (byte) '=';
+    private int readPos;
 
     /**
-     * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as
-     * indices.
-     * <p>
-     * For example, <code>base64Alphabet['+']</code> returns <code>62</code>.
-     * </p>
-     * <p>
-     * The value of undefined encodings is <code>-1</code>.
-     * </p>
+     * Variable tracks how many characters have been written to the current line.
+     * Only used when encoding.  We use it to make sure each encoded line never
+     * goes beyond lineLength (if lineLength >= 0).
      */
-    private static final byte[] base64Alphabet = new byte[BASELENGTH];
+    private int currentLinePos;
+
+    /**
+     * Writes to the buffer only occur after every 3 reads when encoding, an
+     * every 4 reads when decoding.  This variable helps track that.
+     */
+    private int modulus;
+
+    /**
+     * Boolean flag to indicate the EOF has been reached.  Once EOF has been
+     * reached, this Base64 object becomes useless, and must be thrown away.
+     */
+    private boolean eof;
+
+    /**
+     * Place holder for the 3 bytes we're dealing with for our base64 logic.
+     * Bitwise operations store and extract the base64 encoding or decoding from
+     * this variable.
+     */
+    private int x;
+
+    /**
+     * Default constructor:  lineLength is 76, and the lineSeparator is CRLF
+     * when encoding, and all forms can be decoded.
+     */
+    Base64() {
+        this(CHUNK_SIZE, CHUNK_SEPARATOR);
+    }
 
     /**
      * <p>
-     * Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through
-     * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and
-     * <code>/</code>.
-     * </p>
-     * <p>
-     * This array is accessed by using character values as indices.
+     * Consumer can use this constructor to choose a different lineLength
+     * when encoding (lineSeparator is still CRLF).  All forms of data can
+     * be decoded.
+     * </p><p>
+     * Note:  lineLengths that aren't multiples of 4 will still essentially
+     * end up being multiples of 4 in the encoded data.
      * </p>
+     *
+     * @param lineLength each line of encoded data will be at most this long
+     * (rounded up to nearest multiple of 4).  Ignored when decoding.
+     */
+    Base64(int lineLength) {
+        this(lineLength, CHUNK_SEPARATOR);
+    }
+
+    /**
      * <p>
-     * For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.
+     * Consumer can use this constructor to choose a different lineLength
+     * and lineSeparator when encoding.  All forms of data can
+     * be decoded.
+     * </p><p>
+     * Note:  lineLengths that aren't multiples of 4 will still essentially
+     * end up being multiples of 4 in the encoded data.
      * </p>
+     * @param lineLength    Each line of encoded data will be at most this long
+     *                      (rounded up to nearest multiple of 4).  Ignored when decoding.
+     * @param lineSeparator Each line of encoded data will end with this
+     *                      sequence of bytes.
+     * @throws IllegalArgumentException The provided lineSeparator included
+     *                                  some base64 characters.  That's not going to work!
+     */
+    Base64(int lineLength, byte[] lineSeparator) {
+        this.lineLength = lineLength;
+        this.lineSeparator = lineSeparator;
+        if (lineLength > 0) {
+            this.encodeSize = (byte) (4 + lineSeparator.length);
+        } else {
+            this.encodeSize = 4;
+        }
+        this.decodeSize = encodeSize - 1;
+        byte[] separator = discardWhitespace(lineSeparator);
+        if (separator.length > 0 && isArrayByteBase64(separator)) {
+            String sep;
+            try {
+                sep = new String(lineSeparator, "UTF-8");
+            } catch (UnsupportedEncodingException uee) {
+                sep = new String(lineSeparator);
+            }
+            throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
+        }
+    }
+
+    /**
+     * This array is a lookup table that translates 6-bit positive integer
+     * index values into their "Base64 Alphabet" equivalents as specified
+     * in Table 1 of RFC 2045.
+     *
+     * Thanks to "commons" project in ws.apache.org for this code. 
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     */
+    private static final byte[] intToBase64 = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+    };
+
+    /**
+     * This array is a lookup table that translates unicode characters
+     * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
+     * into their 6-bit positive integer equivalents.  Characters that
+     * are not in the Base64 alphabet but fall within the bounds of the
+     * array are translated to -1.
+     *
+     * Thanks to "commons" project in ws.apache.org for this code.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ 
+     */
+    private static final byte[] base64ToInt = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
+            55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
+            5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+            24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+            35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+    };
+
+    /**
+     * Returns true if this Base64 object has buffered data for reading.
+     *
+     * @return true if there is Base64 object still available for reading.
+     */
+    boolean hasData() { return buf != null; }
+
+    /**
+     * Returns the amount of buffered data available for reading.
+     *
+     * @return The amount of buffered data available for reading.
      */
-    private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
+    int avail() { return buf != null ? pos - readPos : 0; }
 
-    // Populating the lookup and character arrays
-    static {
-        for (int i = 0; i < BASELENGTH; i++) {
-            base64Alphabet[i] = (byte) -1;
+    /** Doubles our buffer. */
+    private void resizeBuf() {
+        if (buf == null) {
+            buf = new byte[8192];
+            pos = 0;
+            readPos = 0;
+        } else {
+            byte[] b = new byte[buf.length * 2];
+            System.arraycopy(buf, 0, b, 0, buf.length);
+            buf = b;
         }
-        for (int i = 'Z'; i >= 'A'; i--) {
-            base64Alphabet[i] = (byte) (i - 'A');
+    }
+
+    /**
+     * Extracts buffered data into the provided byte[] array, starting
+     * at position bPos, up to a maximum of bAvail bytes.  Returns how
+     * many bytes were actually extracted.
+     *
+     * @param b      byte[] array to extract the buffered data into.
+     * @param bPos   position in byte[] array to start extraction at.
+     * @param bAvail amount of bytes we're allowed to extract.  We may extract
+     *               fewer (if fewer are available).
+     * @return The number of bytes successfully extracted into the provided
+     *         byte[] array.
+     */
+    int readResults(byte[] b, int bPos, int bAvail) {
+        if (buf != null) {
+            int len = Math.min(avail(), bAvail);
+            if (buf != b) {
+                System.arraycopy(buf, readPos, b, bPos, len);
+                readPos += len;
+                if (readPos >= pos) {
+                    buf = null;
+                }
+            } else {
+                // Re-using the original consumer's output array is only
+                // allowed for one round.
+                buf = null;
+            }
+            return len;
+        } else {
+            return eof ? -1 : 0;
         }
-        for (int i = 'z'; i >= 'a'; i--) {
-            base64Alphabet[i] = (byte) (i - 'a' + 26);
+    }
+
+    /**
+     * Small optimization where we try to buffer directly to the consumer's
+     * output array for one round (if consumer calls this method first!) instead
+     * of starting our own buffer.
+     *
+     * @param out byte[] array to buffer directly to.
+     * @param outPos Position to start buffering into.
+     * @param outAvail Amount of bytes available for direct buffering.
+     */
+    void setInitialBuffer(byte[] out, int outPos, int outAvail) {
+        // We can re-use consumer's original output array under
+        // special circumstances, saving on some System.arraycopy().
+        if (out != null && out.length == outAvail) {
+            buf = out;
+            pos = outPos;
+            readPos = outPos;
         }
-        for (int i = '9'; i >= '0'; i--) {
-            base64Alphabet[i] = (byte) (i - '0' + 52);
+    }
+
+    /**
+     * <p>
+     * Encodes all of the provided data, starting at inPos, for inAvail bytes.
+     * Must be called at least twice:  once with the data to encode, and once
+     * with inAvail set to "-1" to alert encoder that EOF has been reached,
+     * so flush last remaining bytes (if not multiple of 3).
+     * </p><p>
+     * Thanks to "commons" project in ws.apache.org for the bitwise operations,
+     * and general approach.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     * </p>
+     *
+     * @param in byte[] array of binary data to base64 encode.
+     * @param inPos Position to start reading data from.
+     * @param inAvail Amount of bytes available from input for encoding.
+     */
+    void encode(byte[] in, int inPos, int inAvail) {
+        if (eof) {
+            return;
         }
 
-        base64Alphabet['+'] = 62;
-        base64Alphabet['/'] = 63;
+        // inAvail < 0 is how we're informed of EOF in the underlying data we're
+        // encoding.
+        if (inAvail < 0) {
+            eof = true;
+            if (buf == null || buf.length - pos < encodeSize) {
+                resizeBuf();
+            }
+            switch (modulus) {
+                case 1:
+                    buf[pos++] = intToBase64[(x >> 2) & 0x3f];
+                    buf[pos++] = intToBase64[(x << 4) & 0x3f];
+                    buf[pos++] = PAD;
+                    buf[pos++] = PAD;
+                    break;
 
-        for (int i = 0; i <= 25; i++) {
-            lookUpBase64Alphabet[i] = (byte) ('A' + i);
+                case 2:
+                    buf[pos++] = intToBase64[(x >> 10) & 0x3f];
+                    buf[pos++] = intToBase64[(x >> 4) & 0x3f];
+                    buf[pos++] = intToBase64[(x << 2) & 0x3f];
+                    buf[pos++] = PAD;
+                    break;
+            }
+            if (lineLength > 0) {
+                System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
+                pos += lineSeparator.length;
+            }
+        } else {
+            for (int i = 0; i < inAvail; i++) {
+                if (buf == null || buf.length - pos < encodeSize) {
+                    resizeBuf();
+                }
+                modulus = (++modulus) % 3;
+                int b = in[inPos++];
+                if (b < 0) { b += 256; }
+                x = (x << 8) + b;
+                if (0 == modulus) {
+                    buf[pos++] = intToBase64[(x >> 18) & 0x3f];
+                    buf[pos++] = intToBase64[(x >> 12) & 0x3f];
+                    buf[pos++] = intToBase64[(x >> 6) & 0x3f];
+                    buf[pos++] = intToBase64[x & 0x3f];
+                    currentLinePos += 4;
+                    if (lineLength > 0 && lineLength <= currentLinePos) {
+                        System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
+                        pos += lineSeparator.length;
+                        currentLinePos = 0;
+                    }
+                }
+            }
         }
+    }
 
-        for (int i = 26, j = 0; i <= 51; i++, j++) {
-            lookUpBase64Alphabet[i] = (byte) ('a' + j);
-        }
+    /**
+     * <p>
+     * Decodes all of the provided data, starting at inPos, for inAvail bytes.
+     * Should be called at least twice:  once with the data to decode, and once
+     * with inAvail set to "-1" to alert decoder that EOF has been reached.
+     * The "-1" call is not necessary when decoding, but it doesn't hurt, either.
+     * </p><p>
+     * Ignores all non-base64 characters.  This is how chunked (e.g. 76 character)
+     * data is handled, since CR and LF are silently ignored, but has implications
+     * for other bytes, too.  This method subscribes to the garbage-in, garbage-out
+     * philosophy:  it will not check the provided data for validity.
+     * </p><p>
+     * Thanks to "commons" project in ws.apache.org for the bitwise operations,
+     * and general approach.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     * </p>
 
-        for (int i = 52, j = 0; i <= 61; i++, j++) {
-            lookUpBase64Alphabet[i] = (byte) ('0' + j);
+     * @param in byte[] array of ascii data to base64 decode.
+     * @param inPos Position to start reading data from.
+     * @param inAvail Amount of bytes available from input for encoding.
+     */    
+    void decode(byte[] in, int inPos, int inAvail) {
+        if (eof) {
+            return;
+        }
+        if (inAvail < 0) {
+            eof = true;
+        }
+        for (int i = 0; i < inAvail; i++) {
+            if (buf == null || buf.length - pos < decodeSize) {
+                resizeBuf();
+            }
+            byte b = in[inPos++];
+            if (b == PAD) {
+                modulus = (++modulus) % 4;
+                x = x << 6;
+                switch (modulus) {
+                    case 3:
+                        x = x << 6;
+                    case 0:
+                        buf[pos++] = (byte) ((x >> 16) & 0xff);
+                        if (modulus == 0) {
+                            buf[pos++] = (byte) ((x >> 8) & 0xff);
+                        }
+                    default:
+                        // WE'RE DONE!!!!
+                        eof = true;
+                        return;
+                }
+            } else {
+                if (b >= 0 && b < base64ToInt.length) {
+                    int result = base64ToInt[b];
+                    if (result >= 0) {
+                        modulus = (byte) ((++modulus) % 4);
+                        x = (x << 6) + result;
+                        if (modulus == 0) {
+                            buf[pos++] = (byte) ((x >> 16) & 0xff);
+                            buf[pos++] = (byte) ((x >> 8) & 0xff);
+                            buf[pos++] = (byte) (x & 0xff);
+                        }
+                    }
+                }
+            }
         }
-
-        lookUpBase64Alphabet[62] = (byte) '+';
-        lookUpBase64Alphabet[63] = (byte) '/';
     }
 
     /**
@@ -167,13 +455,7 @@
      * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
      */
     private static boolean isBase64(byte octect) {
-        if (octect == PAD) {
-            return true;
-        } else if (octect < 0 || base64Alphabet[octect] == -1) {
-            return false;
-        } else {
-            return true;
-        }
+        return octect == PAD || (octect >= 0 && octect < base64ToInt.length && base64ToInt[octect] != -1);
     }
 
     /**
@@ -264,189 +546,59 @@
      *             Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
      */
     public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
-        long binaryDataLength = binaryData.length;
-        long lengthDataBits = binaryDataLength * EIGHTBIT;
-        long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
-        long tripletCount = lengthDataBits / TWENTYFOURBITGROUP;
-        long encodedDataLengthLong = 0;
-        int chunckCount = 0;
-
-        if (fewerThan24bits != 0) {
-            // data not divisible by 24 bit
-            encodedDataLengthLong = (tripletCount + 1) * 4;
-        } else {
-            // 16 or 8 bit
-            encodedDataLengthLong = tripletCount * 4;
+        if (binaryData == null || binaryData.length == 0) {
+            return binaryData;
         }
+        Base64 b64 = isChunked ? new Base64() : new Base64(0);
 
-        // If the output is to be "chunked" into 76 character sections,
-        // for compliance with RFC 2045 MIME, then it is important to
-        // allow for extra length to account for the separator(s)
+        long len = (binaryData.length * 4) / 3;
+        long mod = len % 4;
+        if (mod != 0) {
+            len += 4 - mod;
+        }
         if (isChunked) {
-
-            chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math
-                    .ceil((float) encodedDataLengthLong / CHUNK_SIZE));
-            encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length;
+            len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length;
         }
 
-        if (encodedDataLengthLong > Integer.MAX_VALUE) {
+        if (len > Integer.MAX_VALUE) {
             throw new IllegalArgumentException(
                     "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
         }
-        int encodedDataLength = (int) encodedDataLengthLong;
-        byte encodedData[] = new byte[encodedDataLength];
-
-        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
-
-        int encodedIndex = 0;
-        int dataIndex = 0;
-        int i = 0;
-        int nextSeparatorIndex = CHUNK_SIZE;
-        int chunksSoFar = 0;
-
-        // log.debug("number of triplets = " + numberTriplets);
-        for (i = 0; i < tripletCount; i++) {
-            dataIndex = i * 3;
-            b1 = binaryData[dataIndex];
-            b2 = binaryData[dataIndex + 1];
-            b3 = binaryData[dataIndex + 2];
-
-            // log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
-
-            l = (byte) (b2 & 0x0f);
-            k = (byte) (b1 & 0x03);
-
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
-            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
-
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            // log.debug( "val2 = " + val2 );
-            // log.debug( "k4 = " + (k<<4) );
-            // log.debug( "vak = " + (val2 | (k<<4)) );
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
-            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
-            encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
-
-            encodedIndex += 4;
-
-            // If we are chunking, let's put a chunk separator down.
-            if (isChunked) {
-                // this assumes that CHUNK_SIZE % 4 == 0
-                if (encodedIndex == nextSeparatorIndex) {
-                    System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
-                    chunksSoFar++;
-                    nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
-                    encodedIndex += CHUNK_SEPARATOR.length;
-                }
-            }
-        }
-
-        // form integral number of 6-bit groups
-        dataIndex = i * 3;
-
-        if (fewerThan24bits == EIGHTBIT) {
-            b1 = binaryData[dataIndex];
-            k = (byte) (b1 & 0x03);
-            // log.debug("b1=" + b1);
-            // log.debug("b1<<2 = " + (b1>>2) );
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
-            encodedData[encodedIndex + 2] = PAD;
-            encodedData[encodedIndex + 3] = PAD;
-        } else if (fewerThan24bits == SIXTEENBIT) {
-
-            b1 = binaryData[dataIndex];
-            b2 = binaryData[dataIndex + 1];
-            l = (byte) (b2 & 0x0f);
-            k = (byte) (b1 & 0x03);
-
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
-
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
-            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
-            encodedData[encodedIndex + 3] = PAD;
-        }
-
-        if (isChunked) {
-            // we also add a separator to the end of the final chunk.
-            if (chunksSoFar < chunckCount) {
-                System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
-                        CHUNK_SEPARATOR.length);
-            }
+        byte[] buf = new byte[(int) len];
+        b64.setInitialBuffer(buf, 0, buf.length);
+        b64.encode(binaryData, 0, binaryData.length);
+        b64.encode(binaryData, 0, -1); // Notify encoder of EOF.
+
+        // Encoder might have resized, even though it was unnecessary.
+        if (b64.buf != buf) {
+            b64.readResults(buf, 0, buf.length);
         }
-
-        return encodedData;
+        return buf;
     }
 
     /**
      * Decodes Base64 data into octects
-     * 
-     * @param base64Data
-     *            Byte array containing Base64 data
+     *
+     * @param base64Data Byte array containing Base64 data
      * @return Array containing decoded data.
      */
     public static byte[] decodeBase64(byte[] base64Data) {
-        // RFC 2045 requires that we discard ALL non-Base64 characters
-        base64Data = discardNonBase64(base64Data);
-
-        // handle the edge case, so we don't have to worry about it later
-        if (base64Data.length == 0) {
-            return new byte[0];
-        }
-
-        int numberQuadruple = base64Data.length / FOURBYTE;
-        byte decodedData[] = null;
-        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
-
-        // Throw away anything not in base64Data
-
-        int encodedIndex = 0;
-        int dataIndex = 0;
-        {
-            // this sizes the output array properly - rlw
-            int lastData = base64Data.length;
-            // ignore the '=' padding
-            while (base64Data[lastData - 1] == PAD) {
-                if (--lastData == 0) {
-                    return new byte[0];
-                }
-            }
-            decodedData = new byte[lastData - numberQuadruple];
+        if (base64Data == null || base64Data.length == 0) {
+            return base64Data;
         }
+        Base64 b64 = new Base64();
 
-        for (int i = 0; i < numberQuadruple; i++) {
-            dataIndex = i * 4;
-            marker0 = base64Data[dataIndex + 2];
-            marker1 = base64Data[dataIndex + 3];
-
-            b1 = base64Alphabet[base64Data[dataIndex]];
-            b2 = base64Alphabet[base64Data[dataIndex + 1]];
-
-            if (marker0 != PAD && marker1 != PAD) {
-                // No PAD e.g 3cQl
-                b3 = base64Alphabet[marker0];
-                b4 = base64Alphabet[marker1];
-
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
-            } else if (marker0 == PAD) {
-                // Two PAD e.g. 3c[Pad][Pad]
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-            } else if (marker1 == PAD) {
-                // One PAD e.g. 3cQ[Pad]
-                b3 = base64Alphabet[marker0];
-
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-            }
-            encodedIndex += 3;
-        }
-        return decodedData;
+        long len = (base64Data.length * 3) / 4;
+        byte[] buf = new byte[(int) len];
+        b64.setInitialBuffer(buf, 0, buf.length);
+        b64.decode(base64Data, 0, base64Data.length);
+        b64.decode(base64Data, 0, -1); // Notify decoder of EOF.
+
+        // We have no idea what the line-length was, so we
+        // cannot know how much of our array wasn't used.
+        byte[] result = new byte[b64.pos];
+        b64.readResults(result, 0, result.length);
+        return result;
     }
 
     /**

Added: commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64InputStream.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64InputStream.java?rev=669256&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64InputStream.java (added)
+++ commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64InputStream.java Wed Jun 18 12:16:03 2008
@@ -0,0 +1,148 @@
+/*
+ * 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
+ *
+ *      http://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 java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides Base64 encoding and decoding in a streaming fashion (unlimited size).
+ * When encoding the default lineLength is 76 characters and the default
+ * lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ * <p>
+ * The default behaviour of the Base64InputStream is to DECODE, whereas the
+ * default behaviour of the Base64OutputStream is to ENCODE, but this
+ * behaviour can be overridden by using a different constructor.
+ * </p><p>
+ * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
+ * </p>
+ *
+ * @author Apache Software Foundation
+ * @version $Id $
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @since 1.0-dev
+ */
+public class Base64InputStream extends FilterInputStream {
+
+    private final boolean doEncode;
+    private final Base64 base64;
+    private final byte[] singleByte = new byte[1];
+
+    /**
+     * Creates a Base64InputStream such that all data read is Base64-decoded
+     * from the original provided InputStream.
+     *
+     * @param in InputStream to wrap.
+     */
+    public Base64InputStream(InputStream in) {
+        this(in, false);
+    }
+
+    /**
+     * Creates a Base64InputStream such that all data read is either
+     * Base64-encoded or Base64-decoded from the original provided InputStream.
+     *
+     * @param in       InputStream to wrap.
+     * @param doEncode true if we should encode all data read from us,
+     *                 false if we should decode.
+     */
+    public Base64InputStream(InputStream in, boolean doEncode) {
+        super(in);
+        this.doEncode = doEncode;
+        this.base64 = new Base64();
+    }
+
+    /**
+     * Creates a Base64InputStream such that all data read is either
+     * Base64-encoded or Base64-decoded from the original provided InputStream.
+     *
+     * @param in            InputStream to wrap.
+     * @param doEncode      true if we should encode all data read from us,
+     *                      false if we should decode.
+     * @param lineLength    If doEncode is true, each line of encoded
+     *                      data will contain lineLength characters.  If
+     *                      doEncode is false, lineLength is ignored.
+     * @param lineSeparator If doEncode is true, each line of encoded
+     *                      data will be terminated with this byte sequence
+     *                      (e.g. \r\n).  If doEncode is false lineSeparator
+     *                      is ignored.
+     */
+    public Base64InputStream(InputStream in, boolean doEncode, int lineLength, byte[] lineSeparator) {
+        super(in);
+        this.doEncode = doEncode;
+        this.base64 = new Base64(lineLength, lineSeparator);
+    }
+
+    /**
+     * Reads one <code>byte</code> from this input stream.
+     * Returns -1 if EOF has been reached.
+     */
+    public int read() throws IOException {
+        int r = read(singleByte, 0, 1);
+        while (r == 0) {
+            r = read(singleByte, 0, 1);
+        }
+        if (r > 0) {
+            return singleByte[0] < 0 ? 256 + singleByte[0] : singleByte[0];
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Attempts to read <code>len</code> bytes into the specified
+     * <code>b</code> array starting at <code>offset</code> from
+     * this InputStream.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public int read(byte b[], int offset, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (offset < 0 || len < 0 || offset + len < 0) {
+            throw new IndexOutOfBoundsException();
+        } else if (offset > b.length || offset + len > b.length) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return 0;
+        } else {
+            if (!base64.hasData()) {
+                byte[] buf = new byte[doEncode ? 4096 : 8192];
+                int c = in.read(buf);
+
+                // A little optimization to avoid System.arraycopy()
+                // when possible.
+                if (c > 0 && b.length == len) {
+                    base64.setInitialBuffer(b, offset, len);
+                }
+
+                if (doEncode) {
+                    base64.encode(buf, 0, c);
+                } else {
+                    base64.decode(buf, 0, c);
+                }
+            }
+            return base64.readResults(b, offset, len);
+        }
+    }
+
+
+}

Added: commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64OutputStream.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64OutputStream.java?rev=669256&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64OutputStream.java (added)
+++ commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64OutputStream.java Wed Jun 18 12:16:03 2008
@@ -0,0 +1,171 @@
+/*
+ * 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
+ *
+ *      http://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 java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Provides Base64 encoding and decoding in a streaming fashion (unlimited size).
+ * When encoding the default lineLength is 76 characters and the default
+ * lineEnding is CRLF, but these can be overridden by using the appropriate
+ * constructor.
+ * <p>
+ * The default behaviour of the Base64OutputStream is to ENCODE, whereas the
+ * default behaviour of the Base64InputStream is to DECODE.  But this behaviour
+ * can be overridden by using a different constructor.
+ * </p><p>
+ * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
+ * </p>
+ *
+ * @author Apache Software Foundation
+ * @version $Id $
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @since 1.0-dev
+ */
+public class Base64OutputStream extends FilterOutputStream {
+    private final boolean doEncode;
+    private final Base64 base64;
+    private final byte[] singleByte = new byte[1];
+
+    /**
+     * Creates a Base64OutputStream such that all data written is Base64-encoded
+     * to the original provided OutputStream.
+     *
+     * @param out OutputStream to wrap.
+     */
+    public Base64OutputStream(OutputStream out) {
+        this(out, true);
+    }
+
+    /**
+     * Creates a Base64OutputStream such that all data written is either
+     * Base64-encoded or Base64-decoded to the original provided OutputStream.
+     *
+     * @param out      OutputStream to wrap.
+     * @param doEncode true if we should encode all data written to us,
+     *                 false if we should decode.
+     */
+    public Base64OutputStream(OutputStream out, boolean doEncode) {
+        super(out);
+        this.doEncode = doEncode;
+        this.base64 = new Base64();
+    }
+
+    /**
+     * Creates a Base64OutputStream such that all data written is either
+     * Base64-encoded or Base64-decoded to the original provided OutputStream.
+     *
+     * @param out           OutputStream to wrap.
+     * @param doEncode      true if we should encode all data written to us,
+     *                      false if we should decode.
+     * @param lineLength    If doEncode is true, each line of encoded
+     *                      data will contain lineLength characters.  If
+     *                      doEncode is false, lineLength is ignored.
+     * @param lineSeparator If doEncode is true, each line of encoded
+     *                      data will be terminated with this byte sequence
+     *                      (e.g. \r\n).  If doEncode is false lineSeparator
+     *                      is ignored.
+     */
+    public Base64OutputStream(OutputStream out, boolean doEncode, int lineLength, byte[] lineSeparator) {
+        super(out);
+        this.doEncode = doEncode;
+        this.base64 = new Base64(lineLength, lineSeparator);
+    }
+
+    /**
+     * Writes the specified <code>byte</code> to this output stream.
+     */
+    public void write(int i) throws IOException {
+        singleByte[0] = (byte) i;
+        write(singleByte, 0, 1);
+    }
+
+    /**
+     * Writes <code>len</code> bytes from the specified
+     * <code>b</code> array starting at <code>offset</code> to
+     * this output stream.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public void write(byte b[], int offset, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (offset < 0 || len < 0 || offset + len < 0) {
+            throw new IndexOutOfBoundsException();
+        } else if (offset > b.length || offset + len > b.length) {
+            throw new IndexOutOfBoundsException();
+        } else if (len > 0) {
+            if (doEncode) {
+                base64.encode(b, offset, len);
+            } else {
+                base64.decode(b, offset, len);
+            }
+            flush(false);
+        }
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be written out to the stream.  If propogate is true, the wrapped
+     * stream will also be flushed.
+     *
+     * @param propogate boolean flag to indicate whether the wrapped
+     *                  OutputStream should also be flushed.
+     * @throws IOException if an I/O error occurs.
+     */
+    private void flush(boolean propogate) throws IOException {
+        int avail = base64.avail();
+        if (avail > 0) {
+            byte[] buf = new byte[avail];
+            int c = base64.readResults(buf, 0, avail);
+            if (c > 0) {
+                out.write(buf, 0, c);
+            }
+        }
+        if (propogate) {
+            out.flush();
+        }
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be written out to the stream.
+     *
+     * @throws IOException if an I/O error occurs.
+     */
+    public void flush() throws IOException { flush(true); }
+
+    /**
+     * Closes this output stream and releases any system resources
+     * associated with the stream.
+     */
+    public void close() throws IOException {
+        // Notify encoder of EOF (-1).
+        if (doEncode) {
+            base64.encode(singleByte, 0, -1);
+        } else {
+            base64.decode(singleByte, 0, -1);
+        }
+        flush();
+        out.close();
+    }
+
+}

Added: commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java?rev=669256&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java (added)
+++ commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64InputStreamTest.java Wed Jun 18 12:16:03 2008
@@ -0,0 +1,212 @@
+/*
+ * 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
+ *
+ *      http://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 junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * @author Apache Software Foundation
+ * @version $Id $
+ */
+public class Base64InputStreamTest extends TestCase {
+
+    private final static byte[] CRLF = {(byte) '\r', (byte) '\n'};
+    private final static byte[] LF = {(byte) '\n'};
+
+    /**
+     * Construct a new instance of this test case.
+     *
+     * @param name Name of the test case
+     */
+    public Base64InputStreamTest(String name) {
+        super(name);
+    }
+
+    /**
+     * Test the Base64InputStream implementation.
+     *
+     * @throws Exception for some failure scenarios.
+     */
+    public void testBase64InputStreamByteByByte() throws Exception {
+        // Hello World test.
+        byte[] encoded = "SGVsbG8gV29ybGQ=\r\n".getBytes("UTF-8");
+        byte[] decoded = "Hello World".getBytes("UTF-8");
+        testByteByByte(encoded, decoded, 76, CRLF);
+
+        // Single Byte test.
+        encoded = "AA==\r\n".getBytes("UTF-8");
+        decoded = new byte[]{(byte) 0};
+        testByteByByte(encoded, decoded, 76, CRLF);
+
+        // OpenSSL interop test.
+        encoded = Base64TestData.ENCODED.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByteByByte(encoded, decoded, 64, LF);
+
+        // Single Line test.
+        String singleLine = Base64TestData.ENCODED.replaceAll("\n", "");
+        encoded = singleLine.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByteByByte(encoded, decoded, 0, LF);
+    }
+
+    /**
+     * Test the Base64InputStream implementation.
+     *
+     * @throws Exception for some failure scenarios.
+     */
+    public void testBase64InputStreamByChunk() throws Exception {
+        // Hello World test.
+        byte[] encoded = "SGVsbG8gV29ybGQ=\r\n".getBytes("UTF-8");
+        byte[] decoded = "Hello World".getBytes("UTF-8");
+        testByChunk(encoded, decoded, 76, CRLF);
+
+        // Single Byte test.
+        encoded = "AA==\r\n".getBytes("UTF-8");
+        decoded = new byte[]{(byte) 0};
+        testByChunk(encoded, decoded, 76, CRLF);
+
+        // OpenSSL interop test.
+        encoded = Base64TestData.ENCODED.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByChunk(encoded, decoded, 64, LF);
+
+        // Single Line test.
+        String singleLine = Base64TestData.ENCODED.replaceAll("\n", "");
+        encoded = singleLine.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByChunk(encoded, decoded, 0, LF);
+    }
+
+
+    /**
+     * Test method does three tests on the supplied data:
+     * 1. encoded ---[DECODE]--> decoded
+     * 2. decoded ---[ENCODE]--> encoded
+     * 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
+     * <p/>
+     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the
+     * Base64InputStream wraps itself in encode and decode mode
+     * over and over again.
+     *
+     * @param encoded   base64 encoded data
+     * @param decoded   the data from above, but decoded
+     * @param chunkSize chunk size (line-length) of the base64 encoded data.
+     * @param seperator Line separator in the base64 encoded data.
+     * @throws Exception Usually signifies a bug in the Base64 commons-codec implementation.
+     */
+    private void testByteByByte(
+            byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator
+    ) throws Exception {
+
+        // Start with encode.
+        InputStream in = new ByteArrayInputStream(decoded);
+        in = new Base64InputStream(in, true, chunkSize, seperator);
+        byte[] output = new byte[encoded.length];
+        for (int i = 0; i < output.length; i++) {
+            output[i] = (byte) in.read();
+        }
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 encode", Arrays.equals(output, encoded));
+
+        // Now let's try decode.
+        in = new ByteArrayInputStream(encoded);
+        in = new Base64InputStream(in);
+        output = new byte[decoded.length];
+        for (int i = 0; i < output.length; i++) {
+            output[i] = (byte) in.read();
+        }
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 decode", Arrays.equals(output, decoded));
+
+        // I always wanted to do this!  (wrap encoder with decoder etc etc).
+        in = new ByteArrayInputStream(decoded);
+        for (int i = 0; i < 10; i++) {
+            in = new Base64InputStream(in, true, chunkSize, seperator);
+            in = new Base64InputStream(in, false);
+        }
+        output = new byte[decoded.length];
+        for (int i = 0; i < output.length; i++) {
+            output[i] = (byte) in.read();
+        }
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
+    }
+
+    /**
+     * Test method does three tests on the supplied data:
+     * 1. encoded ---[DECODE]--> decoded
+     * 2. decoded ---[ENCODE]--> encoded
+     * 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
+     * <p/>
+     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the
+     * Base64InputStream wraps itself in encode and decode mode
+     * over and over again.
+     *
+     * @param encoded   base64 encoded data
+     * @param decoded   the data from above, but decoded
+     * @param chunkSize chunk size (line-length) of the base64 encoded data.
+     * @param seperator Line separator in the base64 encoded data.
+     * @throws Exception Usually signifies a bug in the Base64 commons-codec implementation.
+     */
+    private void testByChunk(
+            byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator
+    ) throws Exception {
+
+        // Start with encode.
+        InputStream in = new ByteArrayInputStream(decoded);
+        in = new Base64InputStream(in, true, chunkSize, seperator);
+        byte[] output = Base64TestData.streamToBytes(in);
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 encode", Arrays.equals(output, encoded));
+
+        // Now let's try decode.
+        in = new ByteArrayInputStream(encoded);
+        in = new Base64InputStream(in);
+        output = Base64TestData.streamToBytes(in);
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 decode", Arrays.equals(output, decoded));
+
+        // I always wanted to do this!  (wrap encoder with decoder etc etc).
+        in = new ByteArrayInputStream(decoded);
+        for (int i = 0; i < 10; i++) {
+            in = new Base64InputStream(in, true, chunkSize, seperator);
+            in = new Base64InputStream(in, false);
+        }
+        output = Base64TestData.streamToBytes(in);
+
+        assertEquals("EOF", -1, in.read());
+        assertEquals("Still EOF", -1, in.read());
+        assertTrue("Streaming base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
+    }
+}

Added: commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64OutputStreamTest.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64OutputStreamTest.java?rev=669256&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64OutputStreamTest.java (added)
+++ commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64OutputStreamTest.java Wed Jun 18 12:16:03 2008
@@ -0,0 +1,208 @@
+/*
+ * 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
+ *
+ *      http://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 junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+/**
+ * @author Apache Software Foundation
+ * @version $Id $
+ */
+public class Base64OutputStreamTest extends TestCase {
+
+    private final static byte[] CRLF = {(byte) '\r', (byte) '\n'};
+    private final static byte[] LF = {(byte) '\n'};
+
+    /**
+     * Construct a new instance of this test case.
+     *
+     * @param name Name of the test case
+     */
+    public Base64OutputStreamTest(String name) {
+        super(name);
+    }
+
+    /**
+     * Test the Base64OutputStream implementation
+     *
+     * @throws Exception for some failure scenarios.
+     */
+    public void testBase64InputStreamByteByByte() throws Exception {
+        // Hello World test.
+        byte[] encoded = "SGVsbG8gV29ybGQ=\r\n".getBytes("UTF-8");
+        byte[] decoded = "Hello World".getBytes("UTF-8");
+        testByteByByte(encoded, decoded, 76, CRLF);
+
+        // Single Byte test.
+        encoded = "AA==\r\n".getBytes("UTF-8");
+        decoded = new byte[]{(byte) 0};
+        testByteByByte(encoded, decoded, 76, CRLF);
+
+        // OpenSSL interop test.
+        encoded = Base64TestData.ENCODED.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByteByByte(encoded, decoded, 64, LF);
+
+        // Single Line test.
+        String singleLine = Base64TestData.ENCODED.replaceAll("\n", "");
+        encoded = singleLine.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByteByByte(encoded, decoded, 0, LF);
+    }
+
+    /**
+     * Test the Base64OutputStream implementation
+     *
+     * @throws Exception for some failure scenarios.
+     */
+    public void testBase64InputStreamByChunk() throws Exception {
+        // Hello World test.
+        byte[] encoded = "SGVsbG8gV29ybGQ=\r\n".getBytes("UTF-8");
+        byte[] decoded = "Hello World".getBytes("UTF-8");
+        testByChunk(encoded, decoded, 76, CRLF);
+
+        // Single Byte test.
+        encoded = "AA==\r\n".getBytes("UTF-8");
+        decoded = new byte[]{(byte) 0};
+        testByChunk(encoded, decoded, 76, CRLF);
+
+        // OpenSSL interop test.
+        encoded = Base64TestData.ENCODED.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByChunk(encoded, decoded, 64, LF);
+
+        // Single Line test.
+        String singleLine = Base64TestData.ENCODED.replaceAll("\n", "");
+        encoded = singleLine.getBytes("UTF-8");
+        decoded = Base64TestData.DECODED;
+        testByChunk(encoded, decoded, 0, LF);
+    }
+
+
+    /**
+     * Test method does three tests on the supplied data:
+     * 1. encoded ---[DECODE]--> decoded
+     * 2. decoded ---[ENCODE]--> encoded
+     * 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
+     * <p/>
+     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the
+     * Base64OutputStream wraps itself in encode and decode mode
+     * over and over again.
+     *
+     * @param encoded   base64 encoded data
+     * @param decoded   the data from above, but decoded
+     * @param chunkSize chunk size (line-length) of the base64 encoded data.
+     * @param seperator Line separator in the base64 encoded data.
+     * @throws Exception Usually signifies a bug in the Base64 commons-codec implementation.
+     */
+    private void testByteByByte(
+            byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator
+    ) throws Exception {
+
+        // Start with encode.
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, seperator);
+        for (int i = 0; i < decoded.length; i++) {
+            out.write(decoded[i]);
+        }
+        out.close();
+        byte[] output = byteOut.toByteArray();
+        assertTrue("Streaming base64 encode", Arrays.equals(output, encoded));
+
+        // Now let's try decode.
+        byteOut = new ByteArrayOutputStream();
+        out = new Base64OutputStream(byteOut, false);
+        for (int i = 0; i < encoded.length; i++) {
+            out.write(encoded[i]);
+        }
+        out.close();
+        output = byteOut.toByteArray();
+        assertTrue("Streaming base64 decode", Arrays.equals(output, decoded));
+
+        // I always wanted to do this!  (wrap encoder with decoder etc etc).
+        byteOut = new ByteArrayOutputStream();
+        out = byteOut;
+        for (int i = 0; i < 10; i++) {
+            out = new Base64OutputStream(out, false);
+            out = new Base64OutputStream(out, true, chunkSize, seperator);
+        }
+        for (int i = 0; i < decoded.length; i++) {
+            out.write(decoded[i]);
+        }
+        out.close();
+        output = byteOut.toByteArray();
+
+        assertTrue("Streaming base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
+    }
+
+    /**
+     * Test method does three tests on the supplied data:
+     * 1. encoded ---[DECODE]--> decoded
+     * 2. decoded ---[ENCODE]--> encoded
+     * 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
+     * <p/>
+     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the
+     * Base64OutputStream wraps itself in encode and decode mode
+     * over and over again.
+     *
+     * @param encoded   base64 encoded data
+     * @param decoded   the data from above, but decoded
+     * @param chunkSize chunk size (line-length) of the base64 encoded data.
+     * @param seperator Line separator in the base64 encoded data.
+     * @throws Exception Usually signifies a bug in the Base64 commons-codec implementation.
+     */
+    private void testByChunk(
+            byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator
+    ) throws Exception {
+
+        // Start with encode.
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, seperator);
+        out.write(decoded);
+        out.close();
+        byte[] output = byteOut.toByteArray();
+        assertTrue("Streaming base64 encode", Arrays.equals(output, encoded));
+
+        // Now let's try decode.
+        byteOut = new ByteArrayOutputStream();
+        out = new Base64OutputStream(byteOut, false);
+        out.write(encoded);
+        out.close();
+        output = byteOut.toByteArray();
+        assertTrue("Streaming base64 decode", Arrays.equals(output, decoded));
+
+        // I always wanted to do this!  (wrap encoder with decoder etc etc).
+        byteOut = new ByteArrayOutputStream();
+        out = byteOut;
+        for (int i = 0; i < 10; i++) {
+            out = new Base64OutputStream(out, false);
+            out = new Base64OutputStream(out, true, chunkSize, seperator);
+        }
+        out.write(decoded);
+        out.close();
+        output = byteOut.toByteArray();
+
+        assertTrue("Streaming base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
+    }
+
+}

Added: commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64TestData.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64TestData.java?rev=669256&view=auto
==============================================================================
--- commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64TestData.java (added)
+++ commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64TestData.java Wed Jun 18 12:16:03 2008
@@ -0,0 +1,159 @@
+/*
+ * 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
+ *
+ *      http://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 java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This random data was encoded by OpenSSL.  Java had nothing to do with it.
+ * This data helps us test interop between Commons-Codec and OpenSSL.  Notice
+ * that OpenSSL creates 64 character lines instead of the 76 of Commons-Codec.
+ *
+ * @author Apache Software Foundation
+ * @version $Id $
+ */
+public class Base64TestData {
+
+    // OpenSSL doesn't include the final \n, but it would be annoying beyond belief
+    // to further parameterize commons-codec to support this pointless variation.
+    public final static String ENCODED
+            = "9IPNKwUvdLiIAp6ctz12SiQmOGstWyYvSPeevufDhrzaws65voykKjbIj33YWTa9\n"
+            + "xA7c/FHypWclrZhQ7onfc3JE93BJ5fT4R9zAEdjbjy1hv4ZYNnET4WJeXMLJ/5p+\n"
+            + "qBpTsPpepW8DNVYy1c02/1wyC+kgA6CvRUd9cSr/lt88AEdsTV4GMCn1+EwuAiYd\n"
+            + "ivxuzn+cLM8q2jewqlI52tP9J7Cs8vqG71s6+WAELKvm/UovvyaOi+OdMUfjQ0JL\n"
+            + "iLkHu6p9OwUgvQqiDKzEv/Augo0dTPZzYGEyCP5GVrle3QQdgciIHnpdd4VUTPGR\n"
+            + "UbXeKbh++U3fbJIng/sQXM3IYByMZ7xt9HWS1LUcRdQ7Prwn/IlQWxOMeq+KZJSo\n"
+            + "AviWtdserXyHbIEa//hmr4p/j80k0g9q35hq1ayGM9984ALTSaZ8WeyFbZx1CxC/\n"
+            + "Qoqf92UH/ylBRnSJNn4sS0oa3uUbNvOnpkB4D9V7Ut9atinCJrw+wiJcMl+9kp25\n"
+            + "1IUxBGA4cUxh0eaxk3ODWnwI95EktmWOKwCSP0xjWwIMxDjygwAG5R8fk9H9bVi1\n"
+            + "thMavm4nDc4vaNoSE1RnZNYwbiUVlVPM9EclvJWTWd6igWeA0MxHAA8iOM5Vnmqp\n"
+            + "/WGM7UDq59rBIdNQCoeTJaAkEtAuLL5zogOa5e+MzVjvB5MYQlOlaaTtQrRApXa5\n"
+            + "Z4VfEanu9UK2fi1T8jJPFC2PmXebxp0bnO+VW+bgyEdIIkIQCaZq1MKWC3KuiOS9\n"
+            + "BJ1t7O0A2JKJKvoE4UNulzV2TGCC+KAnmjRqQBqXlJmgjHQAoHNZKOma/uIQOsvf\n"
+            + "DnqicYdDmfyCYuV89HjA1H8tiDJ85VfsrFHdcbPAoNCpi65awJSHfdPO1NDONOK+\n"
+            + "+S7Y0VXUgoYYrBV4Y7YbC8wg/nqcimr3lm3tRyp+QsgKzdREbfNRk0F5PLyLfsUE\n"
+            + "lepjs1QdV3fEV1LJtiywA3ubVNQJRxhbYxa/C/Xy2qxpm6vvdL92l3q1ccev35Ic\n"
+            + "aOiSx7Im+/GxV2lVKdaOvYVGDD1zBRe6Y2CwQb9p088l3/93qGR5593NCiuPPWcs\n"
+            + "DWwUShM1EyW0FNX1F8bnzHnYijoyE/jf4s/l9bBd7yJdRWRCyih2WcypAiOIEkBs\n"
+            + "H+dCTgalu8sRDoMh4ZIBBdgHfoZUycLqReQFLZZ4Sl4zSmzt5vQxQFhEKb9+ff/4\n"
+            + "rb1KAo6wifengxVfIsa2b5ljXzAqXs7JkPvmC6fa7X4ZZndRokaxYlu3cg8OV+uG\n"
+            + "/6YAHZilo8at0OpkkNdNFuhwuGlkBqrZKNUj/gSiYYc06gF/r/z6iWAjpXJRW1qq\n"
+            + "3CLZXdZFZ/VrqXeVjtOAu2A=\n";
+
+    public final static byte[] DECODED
+            = {-12, -125, -51, 43, 5, 47, 116, -72, -120, 2, -98, -100, -73, 61, 118, 74, 36, 38, 56, 107, 45, 91, 38,
+            47, 72, -9, -98, -66, -25, -61, -122, -68, -38, -62, -50, -71, -66, -116, -92, 42, 54, -56, -113, 125,
+            -40, 89, 54, -67, -60, 14, -36, -4, 81, -14, -91, 103, 37, -83, -104, 80, -18, -119, -33, 115, 114, 68,
+            -9, 112, 73, -27, -12, -8, 71, -36, -64, 17, -40, -37, -113, 45, 97, -65, -122, 88, 54, 113, 19, -31, 98,
+            94, 92, -62, -55, -1, -102, 126, -88, 26, 83, -80, -6, 94, -91, 111, 3, 53, 86, 50, -43, -51, 54, -1, 92,
+            50, 11, -23, 32, 3, -96, -81, 69, 71, 125, 113, 42, -1, -106, -33, 60, 0, 71, 108, 77, 94, 6, 48, 41, -11,
+            -8, 76, 46, 2, 38, 29, -118, -4, 110, -50, 127, -100, 44, -49, 42, -38, 55, -80, -86, 82, 57, -38, -45,
+            -3, 39, -80, -84, -14, -6, -122, -17, 91, 58, -7, 96, 4, 44, -85, -26, -3, 74, 47, -65, 38, -114, -117,
+            -29, -99, 49, 71, -29, 67, 66, 75, -120, -71, 7, -69, -86, 125, 59, 5, 32, -67, 10, -94, 12, -84, -60, -65,
+            -16, 46, -126, -115, 29, 76, -10, 115, 96, 97, 50, 8, -2, 70, 86, -71, 94, -35, 4, 29, -127, -56, -120,
+            30, 122, 93, 119, -123, 84, 76, -15, -111, 81, -75, -34, 41, -72, 126, -7, 77, -33, 108, -110, 39, -125,
+            -5, 16, 92, -51, -56, 96, 28, -116, 103, -68, 109, -12, 117, -110, -44, -75, 28, 69, -44, 59, 62, -68,
+            39, -4, -119, 80, 91, 19, -116, 122, -81, -118, 100, -108, -88, 2, -8, -106, -75, -37, 30, -83, 124, -121,
+            108, -127, 26, -1, -8, 102, -81, -118, 127, -113, -51, 36, -46, 15, 106, -33, -104, 106, -43, -84, -122,
+            51, -33, 124, -32, 2, -45, 73, -90, 124, 89, -20, -123, 109, -100, 117, 11, 16, -65, 66, -118, -97, -9,
+            101, 7, -1, 41, 65, 70, 116, -119, 54, 126, 44, 75, 74, 26, -34, -27, 27, 54, -13, -89, -90, 64, 120, 15,
+            -43, 123, 82, -33, 90, -74, 41, -62, 38, -68, 62, -62, 34, 92, 50, 95, -67, -110, -99, -71, -44, -123,
+            49, 4, 96, 56, 113, 76, 97, -47, -26, -79, -109, 115, -125, 90, 124, 8, -9, -111, 36, -74, 101, -114, 43,
+            0, -110, 63, 76, 99, 91, 2, 12, -60, 56, -14, -125, 0, 6, -27, 31, 31, -109, -47, -3, 109, 88, -75, -74,
+            19, 26, -66, 110, 39, 13, -50, 47, 104, -38, 18, 19, 84, 103, 100, -42, 48, 110, 37, 21, -107, 83, -52,
+            -12, 71, 37, -68, -107, -109, 89, -34, -94, -127, 103, -128, -48, -52, 71, 0, 15, 34, 56, -50, 85, -98,
+            106, -87, -3, 97, -116, -19, 64, -22, -25, -38, -63, 33, -45, 80, 10, -121, -109, 37, -96, 36, 18, -48,
+            46, 44, -66, 115, -94, 3, -102, -27, -17, -116, -51, 88, -17, 7, -109, 24, 66, 83, -91, 105, -92, -19,
+            66, -76, 64, -91, 118, -71, 103, -123, 95, 17, -87, -18, -11, 66, -74, 126, 45, 83, -14, 50, 79, 20, 45,
+            -113, -103, 119, -101, -58, -99, 27, -100, -17, -107, 91, -26, -32, -56, 71, 72, 34, 66, 16, 9, -90, 106,
+            -44, -62, -106, 11, 114, -82, -120, -28, -67, 4, -99, 109, -20, -19, 0, -40, -110, -119, 42, -6, 4, -31,
+            67, 110, -105, 53, 118, 76, 96, -126, -8, -96, 39, -102, 52, 106, 64, 26, -105, -108, -103, -96, -116,
+            116, 0, -96, 115, 89, 40, -23, -102, -2, -30, 16, 58, -53, -33, 14, 122, -94, 113, -121, 67, -103, -4,
+            -126, 98, -27, 124, -12, 120, -64, -44, 127, 45, -120, 50, 124, -27, 87, -20, -84, 81, -35, 113, -77,
+            -64, -96, -48, -87, -117, -82, 90, -64, -108, -121, 125, -45, -50, -44, -48, -50, 52, -30, -66, -7, 46,
+            -40, -47, 85, -44, -126, -122, 24, -84, 21, 120, 99, -74, 27, 11, -52, 32, -2, 122, -100, -118, 106, -9,
+            -106, 109, -19, 71, 42, 126, 66, -56, 10, -51, -44, 68, 109, -13, 81, -109, 65, 121, 60, -68, -117, 126,
+            -59, 4, -107, -22, 99, -77, 84, 29, 87, 119, -60, 87, 82, -55, -74, 44, -80, 3, 123, -101, 84, -44, 9, 71,
+            24, 91, 99, 22, -65, 11, -11, -14, -38, -84, 105, -101, -85, -17, 116, -65, 118, -105, 122, -75, 113,
+            -57, -81, -33, -110, 28, 104, -24, -110, -57, -78, 38, -5, -15, -79, 87, 105, 85, 41, -42, -114, -67,
+            -123, 70, 12, 61, 115, 5, 23, -70, 99, 96, -80, 65, -65, 105, -45, -49, 37, -33, -1, 119, -88, 100, 121,
+            -25, -35, -51, 10, 43, -113, 61, 103, 44, 13, 108, 20, 74, 19, 53, 19, 37, -76, 20, -43, -11, 23, -58, -25,
+            -52, 121, -40, -118, 58, 50, 19, -8, -33, -30, -49, -27, -11, -80, 93, -17, 34, 93, 69, 100, 66, -54, 40,
+            118, 89, -52, -87, 2, 35, -120, 18, 64, 108, 31, -25, 66, 78, 6, -91, -69, -53, 17, 14, -125, 33, -31, -110,
+            1, 5, -40, 7, 126, -122, 84, -55, -62, -22, 69, -28, 5, 45, -106, 120, 74, 94, 51, 74, 108, -19, -26, -12,
+            49, 64, 88, 68, 41, -65, 126, 125, -1, -8, -83, -67, 74, 2, -114, -80, -119, -9, -89, -125, 21, 95, 34,
+            -58, -74, 111, -103, 99, 95, 48, 42, 94, -50, -55, -112, -5, -26, 11, -89, -38, -19, 126, 25, 102, 119,
+            81, -94, 70, -79, 98, 91, -73, 114, 15, 14, 87, -21, -122, -1, -90, 0, 29, -104, -91, -93, -58, -83, -48,
+            -22, 100, -112, -41, 77, 22, -24, 112, -72, 105, 100, 6, -86, -39, 40, -43, 35, -2, 4, -94, 97, -121, 52,
+            -22, 1, 127, -81, -4, -6, -119, 96, 35, -91, 114, 81, 91, 90, -86, -36, 34, -39, 93, -42, 69, 103, -11,
+            107, -87, 119, -107, -114, -45, -128, -69, 96};
+
+    // Some utility code to help test chunked reads of the InputStream.
+
+    private final static int SIZE_KEY = 0;
+    private final static int LAST_READ_KEY = 1;
+
+    static byte[] streamToBytes(final InputStream in) throws IOException {
+        // new byte[7] is obviously quite slow, but helps exercise the code.
+        byte[] buf = new byte[7];
+        try {
+            int[] status = fill(buf, 0, in);
+            int size = status[SIZE_KEY];
+            int lastRead = status[LAST_READ_KEY];
+            while (lastRead != -1) {
+                buf = resizeArray(buf);
+                status = fill(buf, size, in);
+                size = status[SIZE_KEY];
+                lastRead = status[LAST_READ_KEY];
+            }
+            if (buf.length != size) {
+                byte[] smallerBuf = new byte[size];
+                System.arraycopy(buf, 0, smallerBuf, 0, size);
+                buf = smallerBuf;
+            }
+        }
+        finally {
+            in.close();
+        }
+        return buf;
+    }
+
+    private static int[] fill(final byte[] buf, final int offset, final InputStream in)
+            throws IOException {
+        int read = in.read(buf, offset, buf.length - offset);
+        int lastRead = read;
+        if (read == -1) {
+            read = 0;
+        }
+        while (lastRead != -1 && read + offset < buf.length) {
+            lastRead = in.read(buf, offset + read, buf.length - read - offset);
+            if (lastRead != -1) {
+                read += lastRead;
+            }
+        }
+        return new int[]{offset + read, lastRead};
+    }
+
+    private static byte[] resizeArray(final byte[] bytes) {
+        byte[] biggerBytes = new byte[bytes.length * 2];
+        System.arraycopy(bytes, 0, biggerBytes, 0, bytes.length);
+        return biggerBytes;
+    }
+
+}