FileDocCategorySizeDatePackage
Base64DecodeStream.javaAPI DocphoneME MR2 API (J2ME)7237Wed May 02 18:00:34 BST 2007com.sun.perseus.util

Base64DecodeStream.java

/*
 *
 *
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package com.sun.perseus.util;

import java.io.InputStream;
import java.io.IOException;

/**
 * This class implements a Base64 Character decoder as specified in RFC1113.
 * Unlike some other encoding schemes there is nothing in this encoding that
 * tells the decoder where a buffer starts or stops, so to use it you will need
 * to isolate your encoded data into a single chunk and then feed them
 * this decoder. The simplest way to do that is to read all of the encoded
 * data into a string and then use:
 * <pre>
 *      byte    data[];
 *      InputStream is = new ByteArrayInputStream(data);
 *      is = new Base64DecodeStream(is);
 * </pre>
 *
 * On errors, this class throws a IOException with the following detail
 * strings:
 * <pre>
 *    "Base64DecodeStream: Bad Padding byte (2)."
 *    "Base64DecodeStream: Bad Padding byte (1)."
 * </pre>
 *
 * @author <a href="thomas.deweese@kodak.com">Thomas DeWeese</a>
 * @author      Chuck McManis
 * @version $Id: Base64DecodeStream.java,v 1.2 2006/04/21 06:35:45 st125089 Exp $
 */

public class Base64DecodeStream extends InputStream {
    /**
     * The stream to be decoded
     */
    protected InputStream src;

    /**
     * @param src the Base64 encoded input stream
     */
    public Base64DecodeStream(final InputStream src) {
        this.src = src;
    }

    /**
     * Decode table
     */
    private static final byte[] PEM_ARRAY = new byte[256];
    static {
        for (int i = 0; i < PEM_ARRAY.length; i++) {
            PEM_ARRAY[i] = -1;
        }

        int idx = 0;
        for (char c = 'A'; c <= 'Z'; c++) {
            PEM_ARRAY[c] = (byte) idx++;
        }
        for (char c = 'a'; c <= 'z'; c++) {
            PEM_ARRAY[c] = (byte) idx++;
        }

        for (char c = '0'; c <= '9'; c++) {
            PEM_ARRAY[c] = (byte) idx++;
        }

        PEM_ARRAY['+'] = (byte) idx++;
        PEM_ARRAY['/'] = (byte) idx++;
    }

    /**
     * Closes the stream. Note that this <b>does not</b> close
     * the associated <tt>InputStream</tt>
     */
    public void close() {
        eof = true;
    }

    /**
     * @return the number of available bytes
     * @throws IOException if an I/O error occurs
     */
    public int available() 
        throws IOException {
        return 3 - outOffset;
    }

    /**
     * Encoded data buffer
     */
    protected byte[] decodeBuffer = new byte[4];

    /**
     * Output buffer
     */
    protected byte[] outBuffer = new byte[3];

    /**
     * Offset in the out buffer
     */
    protected int  outOffset = 3;

    /**
     * Controls whether the end of the input stream has been reached
     */
    protected boolean eof = false;

    /**
     * @return the next byte of data or -1 if the end of the 
     *         stream is reached.
     * @throws IOException if an I/O error occurs.
     */
    public int read() throws IOException {

        if (outOffset == 3) {
            if (eof || getNextAtom()) {
                eof = true;
                return -1;
            }
        }

        return ((int) outBuffer[outOffset++]) & 0xFF;
    }

    /**
     * @param out where the decoded content should go
     * @param offset in the out array
     * @param len the number of atoms to read
     * @return the number of atoms that were actually read
     * @throws IOException if an I/O error happens
     */
    public int read(final byte[] out, final int offset, final int len)
        throws IOException {

        int idx = 0;
        while (idx < len) {
            if (outOffset == 3) {
                if (eof || getNextAtom()) {
                    eof = true;
                    if (idx == 0) {
                        return -1;
                    } else {
                        return idx;
                    }
                }
            }

            out[offset + idx] = outBuffer[outOffset++];

            idx++;
        }
        return idx;
    }

    /**
     * @return true if the next atom has been read
     * @throws IOException if an I/O error happens
     */
    final boolean getNextAtom() throws IOException {
        int count, a, b, c, d;

        int off = 0;
        while (off != 4) {
            count = src.read(decodeBuffer, off, 4 - off);
            if (count == -1) {
                return true;
            }

            int in = off, out = off;
            while (in < off + count) {
                if ((decodeBuffer[in] != '\n') 
                    && (decodeBuffer[in] != '\r')
                    && (decodeBuffer[in] != ' ')) {
                    decodeBuffer[out++] = decodeBuffer[in];
                }
                in++;
            }

            off = out;
        }

        a = PEM_ARRAY[((int) decodeBuffer[0]) & 0xFF];
        b = PEM_ARRAY[((int) decodeBuffer[1]) & 0xFF];
        c = PEM_ARRAY[((int) decodeBuffer[2]) & 0xFF];
        d = PEM_ARRAY[((int) decodeBuffer[3]) & 0xFF];

        outBuffer[0] = (byte) ((a << 2) | (b >>> 4));
        outBuffer[1] = (byte) ((b << 4) | (c >>> 2));
        outBuffer[2] = (byte) ((c << 6) |  d);

        if (decodeBuffer[3] != '=') {
            // All three bytes are good.
            outOffset = 0;
        } else if (decodeBuffer[2] == '=') {
            // Only one byte of output.
            outBuffer[2] = outBuffer[0];
            outOffset = 2;
            eof = true;
        } else {
            // Only two bytes of output.
            outBuffer[2] = outBuffer[1];
            outBuffer[1] = outBuffer[0];
            outOffset = 1;
            eof = true;
        }
            
        return false;
    }
}