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 }