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.OutputStream;
28  
29  import org.apache.commons.codec.CodecPolicy;
30  import org.junit.jupiter.api.Test;
31  
32  /**
33   * Tests {@link Base32OutputStream}.
34   */
35  class Base32OutputStreamTest extends AbstractBaseNOutputStreamTest {
36  
37      private static final byte[] CR_LF = {(byte) '\r', (byte) '\n'};
38  
39      private static final byte[] LF = {(byte) '\n'};
40  
41  //    /**
42  //     * Test the Base32OutputStream implementation against the special NPE inducing input
43  //     * identified in the CODEC-98 bug.
44  //     *
45  //     * @throws Exception for some failure scenarios.
46  //     */
47  //    @Test
48  //    void testCodec98NPE() throws Exception {
49  //        byte[] codec98 = StringUtils.getBytesUtf8(Base32TestData.CODEC_98_NPE);
50  //        byte[] codec98_1024 = new byte[1024];
51  //        System.arraycopy(codec98, 0, codec98_1024, 0, codec98.length);
52  //        ByteArrayOutputStream data = new ByteArrayOutputStream(1024);
53  //        Base32OutputStream stream = new Base32OutputStream(data, false);
54  //        stream.write(codec98_1024, 0, 1024);
55  //        stream.close();
56  //
57  //        byte[] decodedBytes = data.toByteArray();
58  //        String decoded = StringUtils.newStringUtf8(decodedBytes);
59  //        assertEquals(
60  //            "codec-98 NPE Base32OutputStream", Base32TestData.CODEC_98_NPE_DECODED, decoded
61  //        );
62  //    }
63  
64      @Override
65      OutputStream newOutputStream() {
66          return new Base32OutputStream(new ByteArrayOutputStream());
67      }
68  
69      private void testBase32EmptyOutputStream(final int chunkSize) throws Exception {
70          final byte[] emptyEncoded = {};
71          final byte[] emptyDecoded = {};
72          testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
73          testByChunk(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
74      }
75  
76      /**
77       * Test the Base32OutputStream implementation against empty input.
78       *
79       * @throws Exception
80       *             for some failure scenarios.
81       */
82      @Test
83      void testBase32EmptyOutputStreamMimeChunkSize() throws Exception {
84          testBase32EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
85      }
86  
87      /**
88       * Test the Base32OutputStream implementation against empty input.
89       *
90       * @throws Exception
91       *             for some failure scenarios.
92       */
93      @Test
94      void testBase32EmptyOutputStreamPemChunkSize() throws Exception {
95          testBase32EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
96      }
97  
98      /**
99       * Test the Base32OutputStream implementation
100      *
101      * @throws Exception
102      *             for some failure scenarios.
103      */
104     @Test
105     void testBase32OutputStreamByChunk() throws Exception {
106         // Hello World test.
107         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
108         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
109         testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
110 
111 //        // Single Byte test.
112 //        encoded = StringUtils.getBytesUtf8("AA==\r\n");
113 //        decoded = new byte[]{(byte) 0};
114 //        testByChunk(encoded, decoded, Base32.MIME_CHUNK_SIZE, CRLF);
115 
116 //        // Single Line test.
117 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
118 //        encoded = StringUtils.getBytesUtf8(singleLine);
119 //        decoded = Base32TestData.DECODED;
120 //        testByChunk(encoded, decoded, 0, LF);
121 
122         // test random data of sizes 0 through 150
123         final BaseNCodec codec = new Base32();
124         for (int i = 0; i <= 150; i++) {
125             final byte[][] randomData = BaseNTestData.randomData(codec, i);
126             encoded = randomData[1];
127             decoded = randomData[0];
128             testByChunk(encoded, decoded, 0, LF);
129         }
130     }
131 
132     /**
133      * Test the Base32OutputStream implementation
134      *
135      * @throws Exception
136      *             for some failure scenarios.
137      */
138     @Test
139     void testBase32OutputStreamByteByByte() throws Exception {
140         // Hello World test.
141         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
142         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
143         testByteByByte(encoded, decoded, 76, CR_LF);
144 
145 //        // Single Byte test.
146 //        encoded = StringUtils.getBytesUtf8("AA==\r\n");
147 //        decoded = new byte[]{(byte) 0};
148 //        testByteByByte(encoded, decoded, 76, CRLF);
149 
150 //        // Single Line test.
151 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
152 //        encoded = StringUtils.getBytesUtf8(singleLine);
153 //        decoded = Base32TestData.DECODED;
154 //        testByteByByte(encoded, decoded, 0, LF);
155 
156         // test random data of sizes 0 through 150
157         final BaseNCodec codec = new Base32();
158         for (int i = 0; i <= 150; i++) {
159             final byte[][] randomData = BaseNTestData.randomData(codec, i);
160             encoded = randomData[1];
161             decoded = randomData[0];
162             testByteByByte(encoded, decoded, 0, LF);
163         }
164     }
165 
166     @Test
167     void testBuilder() {
168         assertNotNull(Base32OutputStream.builder().getBaseNCodec());
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 Base32OutputStream wraps itself in encode and decode
176      * mode over and over again.
177      *
178      * @param encoded
179      *            Base32 encoded data
180      * @param decoded
181      *            the data from above, but decoded
182      * @param chunkSize
183      *            chunk size (line-length) of the Base32 encoded data.
184      * @param separator
185      *            Line separator in the Base32 encoded data.
186      * @throws Exception
187      *             Usually signifies a bug in the Base32 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         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
194             out.write(decoded);
195         }
196         byte[] output = byteOut.toByteArray();
197         assertArrayEquals(encoded, output, "Streaming chunked Base32 encode");
198 
199         // Now let's try to decode.
200         byteOut = new ByteArrayOutputStream();
201         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
202             out.write(encoded);
203         }
204         output = byteOut.toByteArray();
205         assertArrayEquals(decoded, output, "Streaming chunked Base32 decode");
206 
207         // I always wanted to do this! (wrap encoder with decoder etc.).
208         byteOut = new ByteArrayOutputStream();
209         OutputStream out = byteOut;
210         for (int i = 0; i < 10; i++) {
211             out = new Base32OutputStream(out, false);
212             out = new Base32OutputStream(out, true, chunkSize, separator);
213         }
214         out.write(decoded);
215         out.close();
216         output = byteOut.toByteArray();
217 
218         assertArrayEquals(decoded, byteOut.toByteArray(), "Streaming chunked Base32 wrap-wrap-wrap!");
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 Base32OutputStream wraps itself in encode and decode
226      * mode over and over again.
227      *
228      * @param encoded
229      *            Base32 encoded data
230      * @param decoded
231      *            the data from above, but decoded
232      * @param chunkSize
233      *            chunk size (line-length) of the Base32 encoded data.
234      * @param separator
235      *            Line separator in the Base32 encoded data.
236      * @throws Exception
237      *             Usually signifies a bug in the Base32 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         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
244             for (final byte element : decoded) {
245                 out.write(element);
246             }
247         }
248         byte[] output = byteOut.toByteArray();
249         assertArrayEquals(encoded, output, "Streaming byte-by-byte Base32 encode");
250 
251         // Now let's try to decode.
252         byteOut = new ByteArrayOutputStream();
253         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
254             for (final byte element : encoded) {
255                 out.write(element);
256             }
257         }
258         output = byteOut.toByteArray();
259         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 decode");
260 
261         // Now let's try to decode with tonnes of flushes.
262         byteOut = new ByteArrayOutputStream();
263         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
264             for (final byte element : encoded) {
265                 out.write(element);
266                 out.flush();
267             }
268         }
269         output = byteOut.toByteArray();
270         assertArrayEquals(decoded, output, "Streaming byte-by-byte flush() Base32 decode");
271 
272         // I always wanted to do this! (wrap encoder with decoder etc.).
273         byteOut = new ByteArrayOutputStream();
274         OutputStream out = byteOut;
275         for (int i = 0; i < 10; i++) {
276             out = new Base32OutputStream(out, false);
277             out = new Base32OutputStream(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         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 wrap-wrap-wrap!");
286     }
287 
288     /**
289      * Test strict decoding.
290      *
291      * @throws Exception
292      *             for some failure scenarios.
293      */
294     @Test
295     void testStrictDecoding() throws Exception {
296         for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
297             final byte[] encoded = StringUtils.getBytesUtf8(s);
298             ByteArrayOutputStream bout = new ByteArrayOutputStream();
299             try (Base32OutputStream out = new Base32OutputStream(bout, false)) {
300                 // Default is lenient decoding; it should not throw
301                 assertFalse(out.isStrictDecoding());
302                 out.write(encoded);
303                 out.close();
304                 assertTrue(bout.size() > 0);
305 
306                 // Strict decoding should throw
307                 bout = new ByteArrayOutputStream();
308                 try (Base32OutputStream out2 = new Base32OutputStream(bout, false, 0, null, CodecPolicy.STRICT)) {
309                     assertTrue(out2.isStrictDecoding());
310                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
311                 }
312                 try (Base32OutputStream out2 = Base32OutputStream.builder()
313                         .setOutputStream(bout).setEncode(false)
314                         .setBaseNCodec(Base32.builder().setLineLength(0).setLineSeparator(null).setDecodingPolicy(CodecPolicy.STRICT).get())
315                         .get()) {
316                     assertTrue(out2.isStrictDecoding());
317                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
318                 }
319                 try (Base32OutputStream out2 = Base32OutputStream.builder()
320                         .setOutputStream(bout).setEncode(false)
321                         .setBaseNCodec(Base32.builder().setDecodingPolicy(CodecPolicy.STRICT).get())
322                         .get()) {
323                     assertTrue(out2.isStrictDecoding());
324                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
325                 }
326             }
327         }
328     }
329 
330     /**
331      * Tests Base32OutputStream.write for expected IndexOutOfBoundsException conditions.
332      *
333      * @throws Exception
334      *             for some failure scenarios.
335      */
336     @Test
337     void testWriteOutOfBounds() throws Exception {
338         final byte[] buf = new byte[1024];
339         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
340         try (Base32OutputStream out = new Base32OutputStream(bout)) {
341             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base32OutputStream.write(buf, -1, 1)");
342             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base32OutputStream.write(buf, 1, -1)");
343             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base32OutputStream.write(buf, buf, buf.length + 1, 0)");
344             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base32OutputStream.write(buf, buf, buf.length - 1, 2)");
345         }
346     }
347 
348     /**
349      * Tests Base32OutputStream.write(null).
350      *
351      * @throws Exception
352      *             for some failure scenarios.
353      */
354     @Test
355     void testWriteToNullCoverage() throws Exception {
356         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
357         try (Base32OutputStream out = new Base32OutputStream(bout)) {
358             assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
359         }
360     }
361 }