001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.binary;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.io.ByteArrayOutputStream;
025import java.io.OutputStream;
026import java.util.Arrays;
027
028import org.junit.Test;
029
030/**
031 * @version $Id $
032 * @since 1.4
033 */
034public class Base64OutputStreamTest {
035
036    private final static byte[] CRLF = {(byte) '\r', (byte) '\n'};
037
038    private final static byte[] LF = {(byte) '\n'};
039
040    private static final String STRING_FIXTURE = "Hello World";
041
042    /**
043     * Test the Base64OutputStream implementation against the special NPE inducing input
044     * identified in the CODEC-98 bug.
045     *
046     * @throws Exception for some failure scenarios.
047     */
048    @Test
049    public void testCodec98NPE() throws Exception {
050        final byte[] codec98 = StringUtils.getBytesUtf8(Base64TestData.CODEC_98_NPE);
051        final byte[] codec98_1024 = new byte[1024];
052        System.arraycopy(codec98, 0, codec98_1024, 0, codec98.length);
053        final ByteArrayOutputStream data = new ByteArrayOutputStream(1024);
054        final Base64OutputStream stream = new Base64OutputStream(data, false);
055        stream.write(codec98_1024, 0, 1024);
056        stream.close();
057
058        final byte[] decodedBytes = data.toByteArray();
059        final String decoded = StringUtils.newStringUtf8(decodedBytes);
060        assertEquals(
061            "codec-98 NPE Base64OutputStream", Base64TestData.CODEC_98_NPE_DECODED, decoded
062        );
063    }
064
065
066    /**
067     * Test the Base64OutputStream implementation against empty input.
068     *
069     * @throws Exception
070     *             for some failure scenarios.
071     */
072    @Test
073    public void testBase64EmptyOutputStreamMimeChunkSize() throws Exception {
074        testBase64EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
075    }
076
077    /**
078     * Test the Base64OutputStream implementation against empty input.
079     *
080     * @throws Exception
081     *             for some failure scenarios.
082     */
083    @Test
084    public void testBase64EmptyOutputStreamPemChunkSize() throws Exception {
085        testBase64EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
086    }
087
088    private void testBase64EmptyOutputStream(final int chunkSize) throws Exception {
089        final byte[] emptyEncoded = new byte[0];
090        final byte[] emptyDecoded = new byte[0];
091        testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CRLF);
092        testByChunk(emptyEncoded, emptyDecoded, chunkSize, CRLF);
093    }
094
095    /**
096     * Test the Base64OutputStream implementation
097     *
098     * @throws Exception
099     *             for some failure scenarios.
100     */
101    @Test
102    public void testBase64OutputStreamByChunk() throws Exception {
103        // Hello World test.
104        byte[] encoded = StringUtils.getBytesUtf8("SGVsbG8gV29ybGQ=\r\n");
105        byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
106        testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
107
108        // Single Byte test.
109        encoded = StringUtils.getBytesUtf8("AA==\r\n");
110        decoded = new byte[]{(byte) 0};
111        testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
112
113        // OpenSSL interop test.
114        encoded = StringUtils.getBytesUtf8(Base64TestData.ENCODED_64_CHARS_PER_LINE);
115        decoded = Base64TestData.DECODED;
116        testByChunk(encoded, decoded, BaseNCodec.PEM_CHUNK_SIZE, LF);
117
118        // Single Line test.
119        final String singleLine = Base64TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
120        encoded = StringUtils.getBytesUtf8(singleLine);
121        decoded = Base64TestData.DECODED;
122        testByChunk(encoded, decoded, 0, LF);
123
124        // test random data of sizes 0 thru 150
125        for (int i = 0; i <= 150; i++) {
126            final byte[][] randomData = Base64TestData.randomData(i, false);
127            encoded = randomData[1];
128            decoded = randomData[0];
129            testByChunk(encoded, decoded, 0, LF);
130        }
131    }
132
133    /**
134     * Test the Base64OutputStream implementation
135     *
136     * @throws Exception
137     *             for some failure scenarios.
138     */
139    @Test
140    public void testBase64OutputStreamByteByByte() throws Exception {
141        // Hello World test.
142        byte[] encoded = StringUtils.getBytesUtf8("SGVsbG8gV29ybGQ=\r\n");
143        byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
144        testByteByByte(encoded, decoded, 76, CRLF);
145
146        // Single Byte test.
147        encoded = StringUtils.getBytesUtf8("AA==\r\n");
148        decoded = new byte[]{(byte) 0};
149        testByteByByte(encoded, decoded, 76, CRLF);
150
151        // OpenSSL interop test.
152        encoded = StringUtils.getBytesUtf8(Base64TestData.ENCODED_64_CHARS_PER_LINE);
153        decoded = Base64TestData.DECODED;
154        testByteByByte(encoded, decoded, 64, LF);
155
156        // Single Line test.
157        final String singleLine = Base64TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
158        encoded = StringUtils.getBytesUtf8(singleLine);
159        decoded = Base64TestData.DECODED;
160        testByteByByte(encoded, decoded, 0, LF);
161
162        // test random data of sizes 0 thru 150
163        for (int i = 0; i <= 150; i++) {
164            final byte[][] randomData = Base64TestData.randomData(i, false);
165            encoded = randomData[1];
166            decoded = randomData[0];
167            testByteByByte(encoded, decoded, 0, LF);
168        }
169    }
170
171    /**
172     * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
173     * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
174     * <p/>
175     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base64OutputStream wraps itself in encode and decode
176     * mode over and over again.
177     *
178     * @param encoded
179     *            base64 encoded data
180     * @param decoded
181     *            the data from above, but decoded
182     * @param chunkSize
183     *            chunk size (line-length) of the base64 encoded data.
184     * @param separator
185     *            Line separator in the base64 encoded data.
186     * @throws Exception
187     *             Usually signifies a bug in the Base64 commons-codec implementation.
188     */
189    private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
190
191        // Start with encode.
192        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
193        OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, separator);
194        out.write(decoded);
195        out.close();
196        byte[] output = byteOut.toByteArray();
197        assertTrue("Streaming chunked base64 encode", Arrays.equals(output, encoded));
198
199        // Now let's try decode.
200        byteOut = new ByteArrayOutputStream();
201        out = new Base64OutputStream(byteOut, false);
202        out.write(encoded);
203        out.close();
204        output = byteOut.toByteArray();
205        assertTrue("Streaming chunked base64 decode", Arrays.equals(output, decoded));
206
207        // I always wanted to do this! (wrap encoder with decoder etc etc).
208        byteOut = new ByteArrayOutputStream();
209        out = byteOut;
210        for (int i = 0; i < 10; i++) {
211            out = new Base64OutputStream(out, false);
212            out = new Base64OutputStream(out, true, chunkSize, separator);
213        }
214        out.write(decoded);
215        out.close();
216        output = byteOut.toByteArray();
217
218        assertTrue("Streaming chunked base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
219    }
220
221    /**
222     * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
223     * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
224     * <p/>
225     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base64OutputStream wraps itself in encode and decode
226     * mode over and over again.
227     *
228     * @param encoded
229     *            base64 encoded data
230     * @param decoded
231     *            the data from above, but decoded
232     * @param chunkSize
233     *            chunk size (line-length) of the base64 encoded data.
234     * @param separator
235     *            Line separator in the base64 encoded data.
236     * @throws Exception
237     *             Usually signifies a bug in the Base64 commons-codec implementation.
238     */
239    private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
240
241        // Start with encode.
242        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
243        OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, separator);
244        for (final byte element : decoded) {
245            out.write(element);
246        }
247        out.close();
248        byte[] output = byteOut.toByteArray();
249        assertTrue("Streaming byte-by-byte base64 encode", Arrays.equals(output, encoded));
250
251        // Now let's try decode.
252        byteOut = new ByteArrayOutputStream();
253        out = new Base64OutputStream(byteOut, false);
254        for (final byte element : encoded) {
255            out.write(element);
256        }
257        out.close();
258        output = byteOut.toByteArray();
259        assertTrue("Streaming byte-by-byte base64 decode", Arrays.equals(output, decoded));
260
261        // Now let's try decode with tonnes of flushes.
262        byteOut = new ByteArrayOutputStream();
263        out = new Base64OutputStream(byteOut, false);
264        for (final byte element : encoded) {
265            out.write(element);
266            out.flush();
267        }
268        out.close();
269        output = byteOut.toByteArray();
270        assertTrue("Streaming byte-by-byte flush() base64 decode", Arrays.equals(output, decoded));
271
272        // I always wanted to do this! (wrap encoder with decoder etc etc).
273        byteOut = new ByteArrayOutputStream();
274        out = byteOut;
275        for (int i = 0; i < 10; i++) {
276            out = new Base64OutputStream(out, false);
277            out = new Base64OutputStream(out, true, chunkSize, separator);
278        }
279        for (final byte element : decoded) {
280            out.write(element);
281        }
282        out.close();
283        output = byteOut.toByteArray();
284
285        assertTrue("Streaming byte-by-byte base64 wrap-wrap-wrap!", Arrays.equals(output, decoded));
286    }
287
288    /**
289     * Tests Base64OutputStream.write for expected IndexOutOfBoundsException conditions.
290     *
291     * @throws Exception
292     *             for some failure scenarios.
293     */
294    @Test
295    public void testWriteOutOfBounds() throws Exception {
296        final byte[] buf = new byte[1024];
297        final ByteArrayOutputStream bout = new ByteArrayOutputStream();
298        final Base64OutputStream out = new Base64OutputStream(bout);
299
300        try {
301            out.write(buf, -1, 1);
302            fail("Expected Base64OutputStream.write(buf, -1, 1) to throw a IndexOutOfBoundsException");
303        } catch (final IndexOutOfBoundsException ioobe) {
304            // Expected
305        }
306
307        try {
308            out.write(buf, 1, -1);
309            fail("Expected Base64OutputStream.write(buf, 1, -1) to throw a IndexOutOfBoundsException");
310        } catch (final IndexOutOfBoundsException ioobe) {
311            // Expected
312        }
313
314        try {
315            out.write(buf, buf.length + 1, 0);
316            fail("Expected Base64OutputStream.write(buf, buf.length + 1, 0) to throw a IndexOutOfBoundsException");
317        } catch (final IndexOutOfBoundsException ioobe) {
318            // Expected
319        }
320
321        try {
322            out.write(buf, buf.length - 1, 2);
323            fail("Expected Base64OutputStream.write(buf, buf.length - 1, 2) to throw a IndexOutOfBoundsException");
324        } catch (final IndexOutOfBoundsException ioobe) {
325            // Expected
326        }
327        out.close();
328    }
329
330    /**
331     * Tests Base64OutputStream.write(null).
332     *
333     * @throws Exception
334     *             for some failure scenarios.
335     */
336    @Test
337    public void testWriteToNullCoverage() throws Exception {
338        final ByteArrayOutputStream bout = new ByteArrayOutputStream();
339        final Base64OutputStream out = new Base64OutputStream(bout);
340        try {
341            out.write(null, 0, 0);
342            fail("Expcted Base64OutputStream.write(null) to throw a NullPointerException");
343        } catch (final NullPointerException e) {
344            // Expected
345        } finally {
346            out.close();
347        }
348    }
349
350}