Base64.java Source code

Java tutorial

Introduction

Here is the source code for Base64.java

Source

/*
 * Copyright 1999,2005 The Apache Software Foundation.
 * 
 * Licensed 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.
 */

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.UndeclaredThrowableException;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/** Performs Base64 encoding and/or decoding. This is an on-the-fly decoder: Unlike,
 * for example, the commons-codec classes, it doesn't depend on byte arrays. In
 * other words, it has an extremely low memory profile. This is well suited even
 * for very large byte streams.
 */
public class Base64 {
    /** An exception of this type is thrown, if the decoded
     * character stream contains invalid input.
     */
    public static class DecodingException extends IOException {
        private static final long serialVersionUID = 3257006574836135478L;

        DecodingException(String pMessage) {
            super(pMessage);
        }
    }

    /** An exception of this type is thrown by the {@link SAXEncoder},
     * if writing to the target handler causes a SAX exception.
     * This class is required, because the {@link IOException}
     * allows no cause until Java 1.3.
     */
    public static class SAXIOException extends IOException {
        private static final long serialVersionUID = 3258131345216451895L;
        final SAXException saxException;

        SAXIOException(SAXException e) {
            super();
            saxException = e;
        }

        /** Returns the encapsulated {@link SAXException}.
         * @return An exception, which was thrown when invoking
         * {@link ContentHandler#characters(char[], int, int)}.
         */
        public SAXException getSAXException() {
            return saxException;
        }
    }

    /** Default line separator: \n
     */
    public static final String LINE_SEPARATOR = "\n";

    /** Default size for line wrapping.
     */
    public static final int LINE_SIZE = 76;

    /**
       * 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.
       */
    private static final char 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.
     */
    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 };

    /** An encoder is an object, which is able to encode byte array
     * in blocks of three bytes. Any such block is converted into an
     * array of four bytes.
     */
    public static abstract class Encoder {
        private int num, numBytes;
        private final char[] charBuffer;
        private int charOffset;
        private final int wrapSize;
        private final int skipChars;
        private final String sep;
        private int lineChars = 0;

        /** Creates a new instance.
         * @param pBuffer The encoders buffer. The encoder will
         * write to the buffer as long as possible. If the
         * buffer is full or the end of data is signaled, then
         * the method {@link #writeBuffer(char[], int, int)}
         * will be invoked.
         * @param pWrapSize A nonzero value indicates, that a line
         * wrap should be performed after the given number of
         * characters. The value must be a multiple of 4. Zero
         * indicates, that no line wrap should be performed.
         * @param pSep The eol sequence being used to terminate
         * a line in case of line wraps. May be null, in which
         * case the default value {@link Base64#LINE_SEPARATOR}
         * is being used.
         */
        protected Encoder(char[] pBuffer, int pWrapSize, String pSep) {
            charBuffer = pBuffer;
            sep = pSep == null ? null : Base64.LINE_SEPARATOR;
            skipChars = pWrapSize == 0 ? 4 : 4 + sep.length();
            wrapSize = skipChars == 4 ? 0 : pWrapSize;
            if (wrapSize < 0 || wrapSize % 4 > 0) {
                throw new IllegalArgumentException(
                        "Illegal argument for wrap size: " + pWrapSize + "(Expected nonnegative multiple of 4)");
            }
            if (pBuffer.length < skipChars) {
                throw new IllegalArgumentException(
                        "The buffer must contain at least " + skipChars + " characters, but has " + pBuffer.length);
            }
        }

        /** Called for writing the buffer contents to the target.
         * @param pChars The buffer being written.
         * @param pOffset Offset of first character being written.
         * @param pLen Number of characters being written.
         * @throws IOException Writing to the destination failed.
         */
        protected abstract void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException;

        private void wrap() {
            for (int j = 0; j < sep.length(); j++) {
                charBuffer[charOffset++] = sep.charAt(j);
            }
            lineChars = 0;
        }

        /** Encodes the given byte array.
         * @param pBuffer Byte array being encoded.
         * @param pOffset Offset of first byte being encoded.
         * @param pLen Number of bytes being encoded.
         * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} method
         * for writing the encoded data failed.
         */
        public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException {
            for (int i = 0; i < pLen; i++) {
                int b = pBuffer[pOffset++];
                if (b < 0) {
                    b += 256;
                }
                num = (num << 8) + b;
                if (++numBytes == 3) {
                    charBuffer[charOffset++] = intToBase64[num >> 18];
                    charBuffer[charOffset++] = intToBase64[(num >> 12) & 0x3f];
                    charBuffer[charOffset++] = intToBase64[(num >> 6) & 0x3f];
                    charBuffer[charOffset++] = intToBase64[num & 0x3f];
                    if (wrapSize > 0) {
                        lineChars += 4;
                        if (lineChars >= wrapSize) {
                            wrap();
                        }
                    }
                    num = 0;
                    numBytes = 0;
                    if (charOffset + skipChars > charBuffer.length) {
                        writeBuffer(charBuffer, 0, charOffset);
                        charOffset = 0;
                    }
                }
            }
        }

        /** Writes any currently buffered data to the destination.
         * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)}
         * method for writing the encoded data failed.
         */
        public void flush() throws IOException {
            if (numBytes > 0) {
                if (numBytes == 1) {
                    charBuffer[charOffset++] = intToBase64[num >> 2];
                    charBuffer[charOffset++] = intToBase64[(num << 4) & 0x3f];
                    charBuffer[charOffset++] = '=';
                    charBuffer[charOffset++] = '=';
                } else {
                    charBuffer[charOffset++] = intToBase64[num >> 10];
                    charBuffer[charOffset++] = intToBase64[(num >> 4) & 0x3f];
                    charBuffer[charOffset++] = intToBase64[(num << 2) & 0x3f];
                    charBuffer[charOffset++] = '=';
                }
                lineChars += 4;
                num = 0;
                numBytes = 0;
            }
            if (wrapSize > 0 && lineChars > 0) {
                wrap();
            }
            if (charOffset > 0) {
                writeBuffer(charBuffer, 0, charOffset);
                charOffset = 0;
            }
        }
    }

    /** An {@link OutputStream}, which is writing to the given
     * {@link Encoder}.
     */
    public static class EncoderOutputStream extends OutputStream {
        private final Encoder encoder;

        /** Creates a new instance, which is creating
         * output using the given {@link Encoder}.
         * @param pEncoder The base64 encoder being used.
         */
        public EncoderOutputStream(Encoder pEncoder) {
            encoder = pEncoder;
        }

        private final byte[] oneByte = new byte[1];

        public void write(int b) throws IOException {
            oneByte[0] = (byte) b;
            encoder.write(oneByte, 0, 1);
        }

        public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException {
            encoder.write(pBuffer, pOffset, pLen);
        }

        public void close() throws IOException {
            encoder.flush();
        }
    }

    /** Returns an {@link OutputStream}, that encodes its input in Base64
     * and writes it to the given {@link Writer}. If the Base64 stream
     * ends, then the output streams {@link OutputStream#close()} method
     * must be invoked. Note, that this will <em>not</em> close the
     * target {@link Writer}!
     * @param pWriter Target writer.
     * @return An output stream, encoding its input in Base64 and writing
     * the output to the writer <code>pWriter</code>.
     */
    public static OutputStream newEncoder(Writer pWriter) {
        return newEncoder(pWriter, LINE_SIZE, LINE_SEPARATOR);
    }

    /** Returns an {@link OutputStream}, that encodes its input in Base64
     * and writes it to the given {@link Writer}. If the Base64 stream
     * ends, then the output streams {@link OutputStream#close()} method
     * must be invoked. Note, that this will <em>not</em> close the
     * target {@link Writer}!
     * @param pWriter Target writer.
     * @param pLineSize Size of one line in characters, must be a multiple
     * of four. Zero indicates, that no line wrapping should occur.
     * @param pSeparator Line separator or null, in which case the default value
     * {@link #LINE_SEPARATOR} is used.
     * @return An output stream, encoding its input in Base64 and writing
     * the output to the writer <code>pWriter</code>.
     */
    public static OutputStream newEncoder(final Writer pWriter, int pLineSize, String pSeparator) {
        final Encoder encoder = new Encoder(new char[4096], pLineSize, pSeparator) {
            protected void writeBuffer(char[] pBuffer, int pOffset, int pLen) throws IOException {
                pWriter.write(pBuffer, pOffset, pLen);
            }
        };
        return new EncoderOutputStream(encoder);
    }

    /** An {@link Encoder}, which is writing to a SAX content handler.
     * This is typically used for embedding a base64 stream into an
     * XML document.
     */
    public static class SAXEncoder extends Encoder {
        private final ContentHandler handler;

        /** Creates a new instance.
         * @param pBuffer The encoders buffer.
         * @param pWrapSize A nonzero value indicates, that a line
         * wrap should be performed after the given number of
         * characters. The value must be a multiple of 4. Zero
         * indicates, that no line wrap should be performed.
         * @param pSep The eol sequence being used to terminate
         * a line in case of line wraps. May be null, in which
         * case the default value {@link Base64#LINE_SEPARATOR}
         * is being used.
         * @param pHandler The target handler.
         */
        public SAXEncoder(char[] pBuffer, int pWrapSize, String pSep, ContentHandler pHandler) {
            super(pBuffer, pWrapSize, pSep);
            handler = pHandler;
        }

        /** Writes to the content handler.
         * @throws SAXIOException Writing to the content handler
         * caused a SAXException.
         */
        protected void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException {
            try {
                handler.characters(pChars, pOffset, pLen);
            } catch (SAXException e) {
                throw new SAXIOException(e);
            }
        }
    }

    /** Converts the given byte array into a base64 encoded character
     * array.
     * @param pBuffer The buffer being encoded.
     * @param pOffset Offset in buffer, where to begin encoding.
     * @param pLength Number of bytes being encoded.
     * @return Character array of encoded bytes.
     */
    public static String encode(byte[] pBuffer, int pOffset, int pLength) {
        return encode(pBuffer, pOffset, pLength, LINE_SIZE, LINE_SEPARATOR);
    }

    /** Converts the given byte array into a base64 encoded character
     * array.
     * @param pBuffer The buffer being encoded.
     * @param pOffset Offset in buffer, where to begin encoding.
     * @param pLength Number of bytes being encoded.
     * @param pLineSize Size of one line in characters, must be a multiple
     * of four. Zero indicates, that no line wrapping should occur.
     * @param pSeparator Line separator or null, in which case the default value
     * {@link #LINE_SEPARATOR} is used.
     * @return Character array of encoded bytes.
     */
    public static String encode(byte[] pBuffer, int pOffset, int pLength, int pLineSize, String pSeparator) {
        StringWriter sw = new StringWriter();
        OutputStream ostream = newEncoder(sw, pLineSize, pSeparator);
        try {
            ostream.write(pBuffer, pOffset, pLength);
            ostream.close();
        } catch (IOException e) {
            throw new UndeclaredThrowableException(e);
        }
        return sw.toString();
    }

    /** Converts the given byte array into a base64 encoded character
     * array with the line size {@link #LINE_SIZE} and the separator
     * {@link #LINE_SEPARATOR}.
     * @param pBuffer The buffer being encoded.
     * @return Character array of encoded bytes.
     */
    public static String encode(byte[] pBuffer) {
        return encode(pBuffer, 0, pBuffer.length);
    }

    /** An encoder is an object, which is able to decode char arrays
     * in blocks of four bytes. Any such block is converted into a
     * array of three bytes.
     */
    public static abstract class Decoder {
        private final byte[] byteBuffer;
        private int byteBufferOffset;
        private int num, numBytes;
        private int eofBytes;

        /** Creates a new instance.
         * @param pBufLen The decoders buffer size. The decoder will
         * store up to this number of decoded bytes before invoking
         * {@link #writeBuffer(byte[],int,int)}.
         */
        protected Decoder(int pBufLen) {
            byteBuffer = new byte[pBufLen];
        }

        /** Called for writing the decoded bytes to the destination.
         * @param pBuffer The byte array being written.
         * @param pOffset Offset of the first byte being written.
         * @param pLen Number of bytes being written.
         * @throws IOException Writing to the destination failed.
         */
        protected abstract void writeBuffer(byte[] pBuffer, int pOffset, int pLen) throws IOException;

        /** Converts the Base64 encoded character array.
         * @param pData The character array being decoded.
         * @param pOffset Offset of first character being decoded.
         * @param pLen Number of characters being decoded.
         * @throws DecodingException Decoding failed.
         * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)}
         * method failed.
         */
        public void write(char[] pData, int pOffset, int pLen) throws IOException {
            for (int i = 0; i < pLen; i++) {
                char c = pData[pOffset++];
                if (Character.isWhitespace(c)) {
                    continue;
                }
                if (c == '=') {
                    ++eofBytes;
                    num = num << 6;
                    switch (++numBytes) {
                    case 1:
                    case 2:
                        throw new DecodingException("Unexpected end of stream character (=)");
                    case 3:
                        // Wait for the next '='
                        break;
                    case 4:
                        byteBuffer[byteBufferOffset++] = (byte) (num >> 16);
                        if (eofBytes == 1) {
                            byteBuffer[byteBufferOffset++] = (byte) (num >> 8);
                        }
                        writeBuffer(byteBuffer, 0, byteBufferOffset);
                        byteBufferOffset = 0;
                        break;
                    case 5:
                        throw new DecodingException("Trailing garbage detected");
                    default:
                        throw new IllegalStateException("Invalid value for numBytes");
                    }
                } else {
                    if (eofBytes > 0) {
                        throw new DecodingException(
                                "Base64 characters after end of stream character (=) detected.");
                    }
                    int result;
                    if (c >= 0 && c < base64ToInt.length) {
                        result = base64ToInt[c];
                        if (result >= 0) {
                            num = (num << 6) + result;
                            if (++numBytes == 4) {
                                byteBuffer[byteBufferOffset++] = (byte) (num >> 16);
                                byteBuffer[byteBufferOffset++] = (byte) ((num >> 8) & 0xff);
                                byteBuffer[byteBufferOffset++] = (byte) (num & 0xff);
                                if (byteBufferOffset + 3 > byteBuffer.length) {
                                    writeBuffer(byteBuffer, 0, byteBufferOffset);
                                    byteBufferOffset = 0;
                                }
                                num = 0;
                                numBytes = 0;
                            }
                            continue;
                        }
                    }
                    if (!Character.isWhitespace(c)) {
                        throw new DecodingException("Invalid Base64 character: " + (int) c);
                    }
                }
            }
        }

        /** Indicates, that no more data is being expected. Writes all currently
         * buffered data to the destination by invoking {@link #writeBuffer(byte[],int,int)}.
         * @throws DecodingException Decoding failed (Unexpected end of file).
         * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} method failed.
         */
        public void flush() throws IOException {
            if (numBytes != 0 && numBytes != 4) {
                throw new DecodingException("Unexpected end of file");
            }
            if (byteBufferOffset > 0) {
                writeBuffer(byteBuffer, 0, byteBufferOffset);
                byteBufferOffset = 0;
            }
        }
    }

    /** Returns a {@link Writer}, that decodes its Base64 encoded
     * input and writes it to the given {@link OutputStream}.
     * Note, that the writers {@link Writer#close()} method will
     * <em>not</em> close the output stream <code>pStream</code>!
     * @param pStream Target output stream.
     * @return An output stream, encoding its input in Base64 and writing
     * the output to the writer <code>pWriter</code>.
     */
    public Writer newDecoder(final OutputStream pStream) {
        return new Writer() {
            private final Decoder decoder = new Decoder(1024) {
                protected void writeBuffer(byte[] pBytes, int pOffset, int pLen) throws IOException {
                    pStream.write(pBytes, pOffset, pLen);
                }
            };

            public void close() throws IOException {
                flush();
            }

            public void flush() throws IOException {
                decoder.flush();
                pStream.flush();
            }

            public void write(char[] cbuf, int off, int len) throws IOException {
                decoder.write(cbuf, off, len);
            }
        };
    }

    /** Converts the given base64 encoded character buffer into a byte array.
     * @param pBuffer The character buffer being decoded.
     * @param pOffset Offset of first character being decoded.
     * @param pLength Number of characters being decoded.
     * @return Converted byte array
     * @throws DecodingException The input character stream contained invalid data.
     */
    public static byte[] decode(char[] pBuffer, int pOffset, int pLength) throws DecodingException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Decoder d = new Decoder(1024) {
            protected void writeBuffer(byte[] pBuf, int pOff, int pLen) throws IOException {
                baos.write(pBuf, pOff, pLen);
            }
        };
        try {
            d.write(pBuffer, pOffset, pLength);
            d.flush();
        } catch (DecodingException e) {
            throw e;
        } catch (IOException e) {
            throw new UndeclaredThrowableException(e);
        }
        return baos.toByteArray();
    }

    /** Converts the given base64 encoded character buffer into a byte array.
     * @param pBuffer The character buffer being decoded.
     * @return Converted byte array
     * @throws DecodingException The input character stream contained invalid data.
     */
    public static byte[] decode(char[] pBuffer) throws DecodingException {
        return decode(pBuffer, 0, pBuffer.length);
    }

    /** Converts the given base64 encoded String into a byte array.
     * @param pBuffer The string being decoded.
     * @return Converted byte array
     * @throws DecodingException The input character stream contained invalid data.
     */
    public static byte[] decode(String pBuffer) throws DecodingException {
        return decode(pBuffer.toCharArray());
    }
}