View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.ByteArrayOutputStream;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  
30  import org.apache.commons.codec.CodecPolicy;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests {@link Base32OutputStream}.
35   */
36  class Base32OutputStreamTest extends AbstractBaseNOutputStreamTest {
37  
38      private static final byte[] CR_LF = {(byte) '\r', (byte) '\n'};
39  
40      private static final byte[] LF = {(byte) '\n'};
41  
42  //    /**
43  //     * Test the Base32OutputStream implementation against the special NPE inducing input
44  //     * identified in the CODEC-98 bug.
45  //     *
46  //     * @throws Exception Thrown for some failure scenarios.
47  //     */
48  //    @Test
49  //    void testCodec98NPE() throws Exception {
50  //        byte[] codec98 = StringUtils.getBytesUtf8(Base32TestData.CODEC_98_NPE);
51  //        byte[] codec98_1024 = new byte[1024];
52  //        System.arraycopy(codec98, 0, codec98_1024, 0, codec98.length);
53  //        ByteArrayOutputStream data = new ByteArrayOutputStream(1024);
54  //        Base32OutputStream stream = new Base32OutputStream(data, false);
55  //        stream.write(codec98_1024, 0, 1024);
56  //        stream.close();
57  //
58  //        byte[] decodedBytes = data.toByteArray();
59  //        String decoded = StringUtils.newStringUtf8(decodedBytes);
60  //        assertEquals(
61  //            "codec-98 NPE Base32OutputStream", Base32TestData.CODEC_98_NPE_DECODED, decoded
62  //        );
63  //    }
64  
65      @Override
66      OutputStream newOutputStream() {
67          return new Base32OutputStream(new ByteArrayOutputStream());
68      }
69  
70      private void testBase32EmptyOutputStream(final int chunkSize) throws Exception {
71          final byte[] emptyEncoded = {};
72          final byte[] emptyDecoded = {};
73          testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
74          testByChunk(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
75      }
76  
77      /**
78       * Test the Base32OutputStream implementation against empty input.
79       *
80       * @throws Exception
81       *             Thrown for some failure scenarios.
82       */
83      @Test
84      void testBase32EmptyOutputStreamMimeChunkSize() throws Exception {
85          testBase32EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
86      }
87  
88      /**
89       * Test the Base32OutputStream implementation against empty input.
90       *
91       * @throws Exception
92       *             Thrown for some failure scenarios.
93       */
94      @Test
95      void testBase32EmptyOutputStreamPemChunkSize() throws Exception {
96          testBase32EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
97      }
98  
99      /**
100      * Test the Base32OutputStream implementation
101      *
102      * @throws Exception
103      *             Thrown for some failure scenarios.
104      */
105     @Test
106     void testBase32OutputStreamByChunk() throws Exception {
107         // Hello World test.
108         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
109         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
110         testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
111 
112 //        // Single Byte test.
113 //        encoded = StringUtils.getBytesUtf8("AA==\r\n");
114 //        decoded = new byte[]{(byte) 0};
115 //        testByChunk(encoded, decoded, Base32.MIME_CHUNK_SIZE, CRLF);
116 
117 //        // Single Line test.
118 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
119 //        encoded = StringUtils.getBytesUtf8(singleLine);
120 //        decoded = Base32TestData.DECODED;
121 //        testByChunk(encoded, decoded, 0, LF);
122 
123         // test random data of sizes 0 through 150
124         final BaseNCodec codec = new Base32();
125         for (int i = 0; i <= 150; i++) {
126             final byte[][] randomData = BaseNTestData.randomData(codec, i);
127             encoded = randomData[1];
128             decoded = randomData[0];
129             testByChunk(encoded, decoded, 0, LF);
130         }
131     }
132 
133     /**
134      * Test the Base32OutputStream implementation
135      *
136      * @throws Exception
137      *             Thrown for some failure scenarios.
138      */
139     @Test
140     void testBase32OutputStreamByteByByte() throws Exception {
141         // Hello World test.
142         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
143         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
144         testByteByByte(encoded, decoded, 76, CR_LF);
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 //        // Single Line test.
152 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
153 //        encoded = StringUtils.getBytesUtf8(singleLine);
154 //        decoded = Base32TestData.DECODED;
155 //        testByteByByte(encoded, decoded, 0, LF);
156 
157         // test random data of sizes 0 through 150
158         final BaseNCodec codec = new Base32();
159         for (int i = 0; i <= 150; i++) {
160             final byte[][] randomData = BaseNTestData.randomData(codec, i);
161             encoded = randomData[1];
162             decoded = randomData[0];
163             testByteByByte(encoded, decoded, 0, LF);
164         }
165     }
166 
167     @Test
168     void testBuilder() {
169         assertNotNull(Base32OutputStream.builder().getBaseNCodec());
170     }
171 
172     /**
173      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
174      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
175      * <p/>
176      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32OutputStream wraps itself in encode and decode
177      * mode over and over again.
178      *
179      * @param encoded
180      *            Base32 encoded data
181      * @param decoded
182      *            the data from above, but decoded
183      * @param chunkSize
184      *            chunk size (line-length) of the Base32 encoded data.
185      * @param separator
186      *            Line separator in the Base32 encoded data.
187      * @throws Exception
188      *             Thrown if a bug occurs in the Base32 commons-codec implementation.
189      */
190     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
191 
192         // Start with encode.
193         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
194         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
195             out.write(decoded);
196         }
197         byte[] output = byteOut.toByteArray();
198         assertArrayEquals(encoded, output, "Streaming chunked Base32 encode");
199 
200         // Now let's try to decode.
201         byteOut = new ByteArrayOutputStream();
202         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
203             out.write(encoded);
204         }
205         output = byteOut.toByteArray();
206         assertArrayEquals(decoded, output, "Streaming chunked Base32 decode");
207 
208         // I always wanted to do this! (wrap encoder with decoder etc.).
209         byteOut = new ByteArrayOutputStream();
210         OutputStream out = byteOut;
211         for (int i = 0; i < 10; i++) {
212             out = new Base32OutputStream(out, false);
213             out = new Base32OutputStream(out, true, chunkSize, separator);
214         }
215         out.write(decoded);
216         out.close();
217         output = byteOut.toByteArray();
218 
219         assertArrayEquals(decoded, byteOut.toByteArray(), "Streaming chunked Base32 wrap-wrap-wrap!");
220     }
221 
222     /**
223      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
224      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
225      * <p/>
226      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32OutputStream wraps itself in encode and decode
227      * mode over and over again.
228      *
229      * @param encoded
230      *            Base32 encoded data
231      * @param decoded
232      *            the data from above, but decoded
233      * @param chunkSize
234      *            chunk size (line-length) of the Base32 encoded data.
235      * @param separator
236      *            Line separator in the Base32 encoded data.
237      * @throws Exception
238      *             Thrown if a bug occurs in the Base32 commons-codec implementation.
239      */
240     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
241 
242         // Start with encode.
243         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
244         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
245             for (final byte element : decoded) {
246                 out.write(element);
247             }
248         }
249         byte[] output = byteOut.toByteArray();
250         assertArrayEquals(encoded, output, "Streaming byte-by-byte Base32 encode");
251 
252         // Now let's try to decode.
253         byteOut = new ByteArrayOutputStream();
254         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
255             for (final byte element : encoded) {
256                 out.write(element);
257             }
258         }
259         output = byteOut.toByteArray();
260         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 decode");
261 
262         // Now let's try to decode with tonnes of flushes.
263         byteOut = new ByteArrayOutputStream();
264         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
265             for (final byte element : encoded) {
266                 out.write(element);
267                 out.flush();
268             }
269         }
270         output = byteOut.toByteArray();
271         assertArrayEquals(decoded, output, "Streaming byte-by-byte flush() Base32 decode");
272 
273         // I always wanted to do this! (wrap encoder with decoder etc.).
274         byteOut = new ByteArrayOutputStream();
275         OutputStream out = byteOut;
276         for (int i = 0; i < 10; i++) {
277             out = new Base32OutputStream(out, false);
278             out = new Base32OutputStream(out, true, chunkSize, separator);
279         }
280         for (final byte element : decoded) {
281             out.write(element);
282         }
283         out.close();
284         output = byteOut.toByteArray();
285 
286         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 wrap-wrap-wrap!");
287     }
288 
289     /**
290      * Test strict decoding.
291      *
292      * @throws Exception
293      *             Thrown for some failure scenarios.
294      */
295     @Test
296     void testStrictDecoding() throws Exception {
297         for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
298             final byte[] encoded = StringUtils.getBytesUtf8(s);
299             ByteArrayOutputStream bout = new ByteArrayOutputStream();
300             try (Base32OutputStream out = new Base32OutputStream(bout, false)) {
301                 // Default is lenient decoding; it should not throw
302                 assertFalse(out.isStrictDecoding());
303                 out.write(encoded);
304                 out.close();
305                 assertTrue(bout.size() > 0);
306 
307                 // Strict decoding should throw
308                 bout = new ByteArrayOutputStream();
309                 try (Base32OutputStream out2 = new Base32OutputStream(bout, false, 0, null, CodecPolicy.STRICT)) {
310                     assertTrue(out2.isStrictDecoding());
311                     final IOException ioe = assertThrows(IOException.class, () -> {
312                         out2.write(encoded);
313                         out2.eof();
314                     });
315                     assertTrue(ioe.getCause() instanceof IllegalArgumentException);
316                 }
317                 try (Base32OutputStream out2 = Base32OutputStream.builder()
318                         .setOutputStream(bout).setEncode(false)
319                         .setBaseNCodec(Base32.builder().setLineLength(0).setLineSeparator(null).setDecodingPolicy(CodecPolicy.STRICT).get())
320                         .get()) {
321                     assertTrue(out2.isStrictDecoding());
322                     final IOException ioe = assertThrows(IOException.class, () -> {
323                         out2.write(encoded);
324                         out2.eof();
325                     });
326                     assertTrue(ioe.getCause() instanceof IllegalArgumentException);
327                 }
328                 try (Base32OutputStream out2 = Base32OutputStream.builder()
329                         .setOutputStream(bout).setEncode(false)
330                         .setBaseNCodec(Base32.builder().setDecodingPolicy(CodecPolicy.STRICT).get())
331                         .get()) {
332                     assertTrue(out2.isStrictDecoding());
333                     final IOException ioe = assertThrows(IOException.class, () -> {
334                         out2.write(encoded);
335                         out2.eof();
336                     });
337                     assertTrue(ioe.getCause() instanceof IllegalArgumentException);
338                 }
339             }
340         }
341     }
342 
343     /**
344      * Tests Base32OutputStream.write for expected IndexOutOfBoundsException conditions.
345      *
346      * @throws Exception
347      *             Thrown for some failure scenarios.
348      */
349     @Test
350     void testWriteOutOfBounds() throws Exception {
351         final byte[] buf = new byte[1024];
352         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
353         try (Base32OutputStream out = new Base32OutputStream(bout)) {
354             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base32OutputStream.write(buf, -1, 1)");
355             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base32OutputStream.write(buf, 1, -1)");
356             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base32OutputStream.write(buf, buf, buf.length + 1, 0)");
357             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base32OutputStream.write(buf, buf, buf.length - 1, 2)");
358         }
359     }
360 
361     /**
362      * Tests Base32OutputStream.write(null).
363      *
364      * @throws Exception
365      *             Thrown for some failure scenarios.
366      */
367     @Test
368     void testWriteToNullCoverage() throws Exception {
369         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
370         try (Base32OutputStream out = new Base32OutputStream(bout)) {
371             assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
372         }
373     }
374 }