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 {
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      private void testBase32EmptyOutputStream(final int chunkSize) throws Exception {
65          final byte[] emptyEncoded = {};
66          final byte[] emptyDecoded = {};
67          testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
68          testByChunk(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
69      }
70  
71      /**
72       * Test the Base32OutputStream implementation against empty input.
73       *
74       * @throws Exception
75       *             for some failure scenarios.
76       */
77      @Test
78      void testBase32EmptyOutputStreamMimeChunkSize() throws Exception {
79          testBase32EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
80      }
81  
82      /**
83       * Test the Base32OutputStream implementation against empty input.
84       *
85       * @throws Exception
86       *             for some failure scenarios.
87       */
88      @Test
89      void testBase32EmptyOutputStreamPemChunkSize() throws Exception {
90          testBase32EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
91      }
92  
93      /**
94       * Test the Base32OutputStream implementation
95       *
96       * @throws Exception
97       *             for some failure scenarios.
98       */
99      @Test
100     void testBase32OutputStreamByChunk() throws Exception {
101         // Hello World test.
102         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
103         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
104         testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
105 
106 //        // Single Byte test.
107 //        encoded = StringUtils.getBytesUtf8("AA==\r\n");
108 //        decoded = new byte[]{(byte) 0};
109 //        testByChunk(encoded, decoded, Base32.MIME_CHUNK_SIZE, CRLF);
110 
111 //        // Single Line test.
112 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
113 //        encoded = StringUtils.getBytesUtf8(singleLine);
114 //        decoded = Base32TestData.DECODED;
115 //        testByChunk(encoded, decoded, 0, LF);
116 
117         // test random data of sizes 0 through 150
118         final BaseNCodec codec = new Base32();
119         for (int i = 0; i <= 150; i++) {
120             final byte[][] randomData = BaseNTestData.randomData(codec, i);
121             encoded = randomData[1];
122             decoded = randomData[0];
123             testByChunk(encoded, decoded, 0, LF);
124         }
125     }
126 
127     /**
128      * Test the Base32OutputStream implementation
129      *
130      * @throws Exception
131      *             for some failure scenarios.
132      */
133     @Test
134     void testBase32OutputStreamByteByByte() throws Exception {
135         // Hello World test.
136         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
137         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
138         testByteByByte(encoded, decoded, 76, CR_LF);
139 
140 //        // Single Byte test.
141 //        encoded = StringUtils.getBytesUtf8("AA==\r\n");
142 //        decoded = new byte[]{(byte) 0};
143 //        testByteByByte(encoded, decoded, 76, CRLF);
144 
145 //        // Single Line test.
146 //        String singleLine = Base32TestData.ENCODED_64_CHARS_PER_LINE.replaceAll("\n", "");
147 //        encoded = StringUtils.getBytesUtf8(singleLine);
148 //        decoded = Base32TestData.DECODED;
149 //        testByteByByte(encoded, decoded, 0, LF);
150 
151         // test random data of sizes 0 through 150
152         final BaseNCodec codec = new Base32();
153         for (int i = 0; i <= 150; i++) {
154             final byte[][] randomData = BaseNTestData.randomData(codec, i);
155             encoded = randomData[1];
156             decoded = randomData[0];
157             testByteByByte(encoded, decoded, 0, LF);
158         }
159     }
160 
161     @Test
162     void testBuilder() {
163         assertNotNull(Base32OutputStream.builder().getBaseNCodec());
164     }
165 
166     /**
167      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
168      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
169      * <p/>
170      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32OutputStream wraps itself in encode and decode
171      * mode over and over again.
172      *
173      * @param encoded
174      *            Base32 encoded data
175      * @param decoded
176      *            the data from above, but decoded
177      * @param chunkSize
178      *            chunk size (line-length) of the Base32 encoded data.
179      * @param separator
180      *            Line separator in the Base32 encoded data.
181      * @throws Exception
182      *             Usually signifies a bug in the Base32 commons-codec implementation.
183      */
184     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
185 
186         // Start with encode.
187         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
188         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
189             out.write(decoded);
190         }
191         byte[] output = byteOut.toByteArray();
192         assertArrayEquals(encoded, output, "Streaming chunked Base32 encode");
193 
194         // Now let's try to decode.
195         byteOut = new ByteArrayOutputStream();
196         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
197             out.write(encoded);
198         }
199         output = byteOut.toByteArray();
200         assertArrayEquals(decoded, output, "Streaming chunked Base32 decode");
201 
202         // I always wanted to do this! (wrap encoder with decoder etc.).
203         byteOut = new ByteArrayOutputStream();
204         OutputStream out = byteOut;
205         for (int i = 0; i < 10; i++) {
206             out = new Base32OutputStream(out, false);
207             out = new Base32OutputStream(out, true, chunkSize, separator);
208         }
209         out.write(decoded);
210         out.close();
211         output = byteOut.toByteArray();
212 
213         assertArrayEquals(decoded, byteOut.toByteArray(), "Streaming chunked Base32 wrap-wrap-wrap!");
214     }
215 
216     /**
217      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
218      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
219      * <p/>
220      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32OutputStream wraps itself in encode and decode
221      * mode over and over again.
222      *
223      * @param encoded
224      *            Base32 encoded data
225      * @param decoded
226      *            the data from above, but decoded
227      * @param chunkSize
228      *            chunk size (line-length) of the Base32 encoded data.
229      * @param separator
230      *            Line separator in the Base32 encoded data.
231      * @throws Exception
232      *             Usually signifies a bug in the Base32 commons-codec implementation.
233      */
234     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
235 
236         // Start with encode.
237         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
238         try (OutputStream out = new Base32OutputStream(byteOut, true, chunkSize, separator)) {
239             for (final byte element : decoded) {
240                 out.write(element);
241             }
242         }
243         byte[] output = byteOut.toByteArray();
244         assertArrayEquals(encoded, output, "Streaming byte-by-byte Base32 encode");
245 
246         // Now let's try to decode.
247         byteOut = new ByteArrayOutputStream();
248         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
249             for (final byte element : encoded) {
250                 out.write(element);
251             }
252         }
253         output = byteOut.toByteArray();
254         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 decode");
255 
256         // Now let's try to decode with tonnes of flushes.
257         byteOut = new ByteArrayOutputStream();
258         try (OutputStream out = new Base32OutputStream(byteOut, false)) {
259             for (final byte element : encoded) {
260                 out.write(element);
261                 out.flush();
262             }
263         }
264         output = byteOut.toByteArray();
265         assertArrayEquals(decoded, output, "Streaming byte-by-byte flush() Base32 decode");
266 
267         // I always wanted to do this! (wrap encoder with decoder etc.).
268         byteOut = new ByteArrayOutputStream();
269         OutputStream out = byteOut;
270         for (int i = 0; i < 10; i++) {
271             out = new Base32OutputStream(out, false);
272             out = new Base32OutputStream(out, true, chunkSize, separator);
273         }
274         for (final byte element : decoded) {
275             out.write(element);
276         }
277         out.close();
278         output = byteOut.toByteArray();
279 
280         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base32 wrap-wrap-wrap!");
281     }
282 
283     /**
284      * Test strict decoding.
285      *
286      * @throws Exception
287      *             for some failure scenarios.
288      */
289     @Test
290     void testStrictDecoding() throws Exception {
291         for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
292             final byte[] encoded = StringUtils.getBytesUtf8(s);
293             ByteArrayOutputStream bout = new ByteArrayOutputStream();
294             try (Base32OutputStream out = new Base32OutputStream(bout, false)) {
295                 // Default is lenient decoding; it should not throw
296                 assertFalse(out.isStrictDecoding());
297                 out.write(encoded);
298                 out.close();
299                 assertTrue(bout.size() > 0);
300 
301                 // Strict decoding should throw
302                 bout = new ByteArrayOutputStream();
303                 try (Base32OutputStream out2 = new Base32OutputStream(bout, false, 0, null, CodecPolicy.STRICT)) {
304                     assertTrue(out2.isStrictDecoding());
305                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
306                 }
307                 try (Base32OutputStream out2 = Base32OutputStream.builder()
308                         .setOutputStream(bout).setEncode(false)
309                         .setBaseNCodec(Base32.builder().setLineLength(0).setLineSeparator(null).setDecodingPolicy(CodecPolicy.STRICT).get())
310                         .get()) {
311                     assertTrue(out2.isStrictDecoding());
312                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
313                 }
314                 try (Base32OutputStream out2 = Base32OutputStream.builder()
315                         .setOutputStream(bout).setEncode(false)
316                         .setBaseNCodec(Base32.builder().setDecodingPolicy(CodecPolicy.STRICT).get())
317                         .get()) {
318                     assertTrue(out2.isStrictDecoding());
319                     assertThrows(IllegalArgumentException.class, () -> out2.write(encoded));
320                 }
321             }
322         }
323     }
324 
325     /**
326      * Tests Base32OutputStream.write for expected IndexOutOfBoundsException conditions.
327      *
328      * @throws Exception
329      *             for some failure scenarios.
330      */
331     @Test
332     void testWriteOutOfBounds() throws Exception {
333         final byte[] buf = new byte[1024];
334         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
335         try (Base32OutputStream out = new Base32OutputStream(bout)) {
336             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base32OutputStream.write(buf, -1, 1)");
337             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base32OutputStream.write(buf, 1, -1)");
338             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base32OutputStream.write(buf, buf, buf.length + 1, 0)");
339             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base32OutputStream.write(buf, buf, buf.length - 1, 2)");
340         }
341     }
342 
343     /**
344      * Tests Base32OutputStream.write(null).
345      *
346      * @throws Exception
347      *             for some failure scenarios.
348      */
349     @Test
350     void testWriteToNullCoverage() throws Exception {
351         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
352         try (Base32OutputStream out = new Base32OutputStream(bout)) {
353             assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
354         }
355     }
356 }