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
018 package org.apache.commons.codec.binary;
019
020 import static org.junit.Assert.assertEquals;
021 import static org.junit.Assert.assertTrue;
022 import static org.junit.Assert.fail;
023
024 import java.io.ByteArrayOutputStream;
025 import java.io.OutputStream;
026 import java.util.Arrays;
027
028 import org.junit.Test;
029
030 /**
031 * @version $Id $
032 * @since 1.4
033 */
034 public 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 seperator
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[] seperator) throws Exception {
190
191 // Start with encode.
192 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
193 OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, seperator);
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, seperator);
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 seperator
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[] seperator) throws Exception {
240
241 // Start with encode.
242 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
243 OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, seperator);
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, seperator);
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 }