Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 092198f5 authored by Doug Zongker's avatar Doug Zongker Committed by Android (Google) Code Review
Browse files

Merge "add Base64InputStream"

parents 0469dbb1 27a31e57
Loading
Loading
Loading
Loading
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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.
 */

package com.android.common;

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

/**
 * An OutputStream that does either Base64 encoding or decoding on the
 * data written to it, writing the resulting data to another
 * OutputStream.
 */
public class Base64InputStream extends FilterInputStream {
    private final boolean encode;
    private final Base64.EncoderState estate;
    private final Base64.DecoderState dstate;

    private static byte[] EMPTY = new byte[0];

    private static final int BUFFER_SIZE = 2048;
    private boolean eof;
    private byte[] inputBuffer;
    private byte[] outputBuffer;
    private int outputStart;
    private int outputEnd;

    /**
     * An InputStream that performs Base64 decoding on the data read
     * from the wrapped stream.
     *
     * @param in the InputStream to read the source data from
     * @param flags bit flags for controlling the decoder; see the
     *        constants in {@link Base64}
     */
    public Base64InputStream(InputStream out, int flags) {
        this(out, flags, false);
    }

    /**
     * Performs Base64 encoding or decoding on the data read from the
     * wrapped InputStream.
     *
     * @param in the InputStream to read the source data from
     * @param flags bit flags for controlling the decoder; see the
     *        constants in {@link Base64}
     * @param encode true to encode, false to decode
     */
    public Base64InputStream(InputStream out, int flags, boolean encode) {
        super(out);
        this.encode = encode;
        eof = false;
        inputBuffer = new byte[BUFFER_SIZE];
        if (encode) {
            // len*8/5+10 is an overestimate of the most bytes the
            // encoder can produce for len bytes of input.
            outputBuffer = new byte[BUFFER_SIZE * 8/5 + 10];
            estate = new Base64.EncoderState(flags, outputBuffer);
            dstate = null;
        } else {
            // len*3/4+10 is an overestimate of the most bytes the
            // decoder can produce for len bytes of input.
            outputBuffer = new byte[BUFFER_SIZE * 3/4 + 10];
            estate = null;
            dstate = new Base64.DecoderState(flags, outputBuffer);
        }
        outputStart = 0;
        outputEnd = 0;
    }

    public boolean markSupported() {
        return false;
    }

    public void mark(int readlimit) {
        throw new UnsupportedOperationException();
    }

    public void reset() {
        throw new UnsupportedOperationException();
    }

    public void close() throws IOException {
        in.close();
        inputBuffer = null;
    }

    public int available() {
        return outputEnd - outputStart;
    }

    public long skip(long n) throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return 0;
        }
        long bytes = Math.min(n, outputEnd-outputStart);
        outputStart += bytes;
        return bytes;
    }

    public int read() throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return -1;
        } else {
            return outputBuffer[outputStart++];
        }
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return -1;
        }
        int bytes = Math.min(len, outputEnd-outputStart);
        System.arraycopy(outputBuffer, outputStart, b, off, bytes);
        outputStart += bytes;
        return bytes;
    }

    /**
     * Read data from the input stream into inputBuffer, then
     * decode/encode it into the empty outputBuffer, and reset the
     * outputStart and outputEnd pointers.
     */
    private void refill() throws IOException {
        if (eof) return;
        int bytesRead = in.read(inputBuffer);
        if (encode) {
            if (bytesRead == -1) {
                eof = true;
                Base64.encodeInternal(EMPTY, 0, 0, estate, true);
            } else {
                Base64.encodeInternal(inputBuffer, 0, bytesRead, estate, false);
            }
            outputEnd = estate.op;
        } else {
            if (bytesRead == -1) {
                eof = true;
                Base64.decodeInternal(EMPTY, 0, 0, dstate, true);
            } else {
                Base64.decodeInternal(inputBuffer, 0, bytesRead, dstate, false);
            }
            outputEnd = dstate.op;
        }
        outputStart = 0;
    }
}
+163 −53
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.common;

import junit.framework.TestCase;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Random;

@@ -228,8 +229,13 @@ public class Base64Test extends TestCase {
    /**
     * Tests that Base64.encodeInternal does correct handling of the
     * tail for each call.
     *
     * This test is disabled because while it passes if you can get it
     * to run, android's test infrastructure currently doesn't allow
     * us to get at package-private members (Base64.EncoderState in
     * this case).
     */
    public void testEncodeInternal() throws Exception {
    public void XXXtestEncodeInternal() throws Exception {
        byte[] input = { (byte) 0x61, (byte) 0x62, (byte) 0x63 };
        byte[] output = new byte[100];

@@ -272,23 +278,8 @@ public class Base64Test extends TestCase {
        assertEquals("YQ".getBytes(), 2, state.output, state.op);
    }

    /**
     * Tests that Base64OutputStream produces exactly the same results
     * as calling Base64.encode/.decode on an in-memory array.
     */
    public void testOutputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // input needs to be at least 1024 bytes to test filling up
        // the write(int) buffer.
        byte[] input = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
    private static final String lipsum =
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
            "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
            "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
            "urna, pharetra vitae consequat eget, adipiscing eu ante. " +
@@ -308,103 +299,222 @@ public class Base64Test extends TestCase {
            "Phasellus posuere, leo at ultricies vehicula, massa risus " +
            "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
            "molestie dapibus commodo. Ut vel tellus at massa gravida " +
                        "semper non sed orci.").getBytes();
            "semper non sed orci.";

    public void testInputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // Test input needs to be at least 2048 bytes to fill up the
        // read buffer of Base64InputStream.
        byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();

        for (int flags: flagses) {
            byte[] encoded = Base64.encode(plain, flags);

            ByteArrayInputStream bais;
            Base64InputStream b64is;
            byte[] actual = new byte[plain.length * 2];
            int ap;
            int b;

            // ----- test decoding ("encoded" -> "plain") -----

            // read as much as it will give us in one chunk
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
                ap += b;
            }
            assertEquals(actual, ap, plain);

            // read individual bytes
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            while ((b = b64is.read()) != -1) {
                actual[ap++] = (byte) b;
            }
            assertEquals(actual, ap, plain);

            // mix reads of variously-sized arrays with one-byte reads
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            readloop: while (true) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                if (l >= 0) {
                    b = b64is.read(actual, ap, l);
                    if (b == -1) break readloop;
                    ap += b;
                } else {
                    for (int i = 0; i < -l; ++i) {
                        if ((b = b64is.read()) == -1) break readloop;
                        actual[ap++] = (byte) b;
                    }
                }
            }
            assertEquals(actual, ap, plain);

        for (int f = 0; f < flagses.length; ++f) {
            int flags = flagses[f];
            // ----- test encoding ("plain" -> "encoded") -----

            byte[] expected = Base64.encode(input, flags);
            // read as much as it will give us in one chunk
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
                ap += b;
            }
            assertEquals(actual, ap, encoded);

            // read individual bytes
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            while ((b = b64is.read()) != -1) {
                actual[ap++] = (byte) b;
            }
            assertEquals(actual, ap, encoded);

            // mix reads of variously-sized arrays with one-byte reads
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            readloop: while (true) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                if (l >= 0) {
                    b = b64is.read(actual, ap, l);
                    if (b == -1) break readloop;
                    ap += b;
                } else {
                    for (int i = 0; i < -l; ++i) {
                        if ((b = b64is.read()) == -1) break readloop;
                        actual[ap++] = (byte) b;
                    }
                }
            }
            assertEquals(actual, ap, encoded);
        }
    }

    /**
     * Tests that Base64OutputStream produces exactly the same results
     * as calling Base64.encode/.decode on an in-memory array.
     */
    public void testOutputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // Test input needs to be at least 1024 bytes to test filling
        // up the write(int) buffer of Base64OutputStream.
        byte[] plain = (lipsum + lipsum).getBytes();

        for (int flags: flagses) {
            byte[] encoded = Base64.encode(plain, flags);

            ByteArrayOutputStream baos;
            Base64OutputStream b64os;
            byte[] actual;
            int p;

            // ----- test encoding ("input" -> "expected") -----
            // ----- test encoding ("plain" -> "encoded") -----

            // one large write(byte[]) of the whole input
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            b64os.write(input);
            b64os.write(plain);
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // many calls to write(int)
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            for (int i = 0; i < input.length; ++i) {
                b64os.write(input[i]);
            for (int i = 0; i < plain.length; ++i) {
                b64os.write(plain[i]);
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // intermixed sequences of write(int) with
            // write(byte[],int,int) of various lengths.
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            p = 0;
            while (p < input.length) {
            while (p < plain.length) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                l = Math.min(l, input.length-p);
                l = Math.min(l, plain.length-p);
                if (l >= 0) {
                    b64os.write(input, p, l);
                    b64os.write(plain, p, l);
                    p += l;
                } else {
                    l = Math.min(-l, input.length-p);
                    l = Math.min(-l, plain.length-p);
                    for (int i = 0; i < l; ++i) {
                        b64os.write(input[p+i]);
                        b64os.write(plain[p+i]);
                    }
                    p += l;
                }
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // ----- test decoding ("expected" -> "input") -----
            // ----- test decoding ("encoded" -> "plain") -----

            // one large write(byte[]) of the whole input
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            b64os.write(expected);
            b64os.write(encoded);
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);

            // many calls to write(int)
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            for (int i = 0; i < expected.length; ++i) {
                b64os.write(expected[i]);
            for (int i = 0; i < encoded.length; ++i) {
                b64os.write(encoded[i]);
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);

            // intermixed sequences of write(int) with
            // write(byte[],int,int) of various lengths.
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            p = 0;
            while (p < expected.length) {
            while (p < encoded.length) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                l = Math.min(l, expected.length-p);
                l = Math.min(l, encoded.length-p);
                if (l >= 0) {
                    b64os.write(expected, p, l);
                    b64os.write(encoded, p, l);
                    p += l;
                } else {
                    l = Math.min(-l, expected.length-p);
                    l = Math.min(-l, encoded.length-p);
                    for (int i = 0; i < l; ++i) {
                        b64os.write(expected[p+i]);
                        b64os.write(encoded[p+i]);
                    }
                    p += l;
                }
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);
        }
    }
}