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.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.InputStream;
29  
30  import org.apache.commons.io.IOUtils;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests {@link Base45InputStream}.
35   */
36  class Base45InputStreamTest {
37  
38      private static final byte[] CRLF = { (byte) '\r', (byte) '\n' };
39      private static final byte[] LF = { (byte) '\n' };
40      private static final String STRING_FIXTURE = "Hello World";
41  
42      @Test
43      void testAvailable() throws Throwable {
44          final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
45          final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
46          try (Base45InputStream b45stream = new Base45InputStream(ins)) {
47              final int initialAvailable = b45stream.available();
48              assertTrue(initialAvailable > 0, "Initial available should be greater than 0");
49              assertEquals(3, b45stream.skip(10), "Skip should return 3 (decoded bytes)");
50              assertEquals(0, b45stream.available());
51              assertEquals(-1, b45stream.read());
52              assertEquals(-1, b45stream.read());
53          }
54      }
55  
56      private void testBase45EmptyInputStream(final int chunkSize) throws Exception {
57          final byte[] emptyEncoded = {};
58          final byte[] emptyDecoded = {};
59          testByChunk(emptyEncoded, emptyDecoded, chunkSize, CRLF);
60          testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CRLF);
61      }
62  
63      /**
64       * Tests the Base45InputStream implementation against empty input.
65       *
66       * @throws Exception Thrown for some failure scenarios.
67       */
68      @Test
69      void testBase45EmptyInputStreamMimeChunkSize() throws Exception {
70          testBase45EmptyInputStream(BaseNCodec.MIME_CHUNK_SIZE);
71      }
72  
73      /**
74       * Tests the Base45InputStream implementation against empty input.
75       *
76       * @throws Exception Thrown for some failure scenarios.
77       */
78      @Test
79      void testBase45EmptyInputStreamPemChunkSize() throws Exception {
80          testBase45EmptyInputStream(BaseNCodec.PEM_CHUNK_SIZE);
81      }
82  
83      @Test
84      void testBase45InputStreamByChunk() throws Exception {
85          // Hello World test.
86          byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
87          byte[] encoded = new Base45().encode(decoded);
88          testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
89          // test random data of sizes 0 through 150
90          final BaseNCodec codec = new Base45();
91          for (int i = 0; i <= 150; i++) {
92              final byte[][] randomData = BaseNTestData.randomData(codec, i);
93              encoded = randomData[1];
94              decoded = randomData[0];
95              testByChunk(encoded, decoded, 0, LF);
96          }
97      }
98  
99      @Test
100     void testBase45InputStreamByteByByte() throws Exception {
101         // Hello World test.
102         byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
103         byte[] encoded = new Base45().encode(decoded);
104         testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
105         // test random data of sizes 0 through 150
106         final BaseNCodec codec = new Base45();
107         for (int i = 0; i <= 150; i++) {
108             final byte[][] randomData = BaseNTestData.randomData(codec, i);
109             encoded = randomData[1];
110             decoded = randomData[0];
111             testByteByByte(encoded, decoded, 0, LF);
112         }
113     }
114 
115     @Test
116     void testBuilder() {
117         assertNotNull(Base45InputStream.builder().getBaseNCodec());
118     }
119 
120     /**
121      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
122      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
123      * <p/>
124      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base45InputStream wraps itself in encode and decode mode over and over again.
125      *
126      * @param encoded   Base45 encoded data
127      * @param decoded   The data from above, but decoded
128      * @param chunkSize chunk size (line-length) of the Base45 encoded data.
129      * @param separator Line separator in the Base45 encoded data.
130      * @throws Exception Thrown if a bug occurs in the Base45 commons-codec implementation.
131      */
132     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
133         try (InputStream in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
134             final byte[] output = IOUtils.toByteArray(in);
135             assertEquals(-1, in.read(), "EOF");
136             assertEquals(-1, in.read(), "Still EOF");
137             assertArrayEquals(encoded, output, "Streaming Base45 encode");
138         }
139         try (InputStream in = new Base45InputStream(new ByteArrayInputStream(encoded))) {
140             final byte[] output = IOUtils.toByteArray(in);
141             assertEquals(-1, in.read(), "EOF");
142             assertEquals(-1, in.read(), "Still EOF");
143             assertArrayEquals(decoded, output, "Streaming Base45 decode");
144         }
145         InputStream in = new ByteArrayInputStream(decoded);
146         for (int i = 0; i < 10; i++) {
147             in = Base45InputStream.builder().setInputStream(in).setEncode(true).get();
148             in = Base45InputStream.builder().setInputStream(in).setEncode(false).get();
149         }
150         final InputStream in1 = in;
151         final byte[] output = IOUtils.toByteArray(in1);
152         assertEquals(-1, in.read(), "EOF");
153         assertEquals(-1, in.read(), "Still EOF");
154         assertArrayEquals(decoded, output, "Streaming Base45 wrap-wrap-wrap!");
155         in.close();
156     }
157 
158     /**
159      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
160      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
161      * <p/>
162      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base45InputStream wraps itself in encode and decode mode over and over again.
163      *
164      * @param encoded   Base45 encoded data
165      * @param decoded   The data from above, but decoded
166      * @param chunkSize chunk size (line-length) of the Base45 encoded data.
167      * @param separator Line separator in the Base45 encoded data.
168      * @throws Exception Thrown if a bug occurs in the Base45 commons-codec implementation.
169      */
170     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
171         InputStream in;
172         in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get();
173         final InputStream in1 = in;
174         byte[] output = IOUtils.toByteArray(in1);
175         assertEquals(-1, in.read(), "EOF");
176         assertEquals(-1, in.read(), "Still EOF");
177         assertArrayEquals(encoded, output, "Streaming Base45 encode");
178         in.close();
179         in = new Base45InputStream(new ByteArrayInputStream(encoded));
180         final InputStream in2 = in;
181         output = IOUtils.toByteArray(in2);
182         assertEquals(-1, in.read(), "EOF");
183         assertEquals(-1, in.read(), "Still EOF");
184         assertArrayEquals(decoded, output, "Streaming Base45 decode");
185         in.close();
186         in = new ByteArrayInputStream(decoded);
187         for (int i = 0; i < 10; i++) {
188             in = Base45InputStream.builder().setInputStream(in).setEncode(true).get();
189             in = Base45InputStream.builder().setInputStream(in).setEncode(false).get();
190         }
191         final InputStream in3 = in;
192         output = IOUtils.toByteArray(in3);
193         assertEquals(-1, in.read(), "EOF");
194         assertEquals(-1, in.read(), "Still EOF");
195         assertArrayEquals(decoded, output, "Streaming Base45 wrap-wrap-wrap!");
196     }
197 
198     /**
199      * Tests markSupported.
200      *
201      * @throws Exception Thrown for some failure scenarios.
202      */
203     @Test
204     void testMarkSupported() throws Exception {
205         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
206         try (Base45InputStream in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
207             // Always returns false for now.
208             assertFalse(in.markSupported(), "Base45InputStream.markSupported() is false");
209         }
210     }
211 
212     /**
213      * Tests read returning 0
214      *
215      * @throws Exception Thrown for some failure scenarios.
216      */
217     @Test
218     void testRead0() throws Exception {
219         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
220         final byte[] buf = new byte[1024];
221         int bytesRead = 0;
222         try (Base45InputStream in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
223             bytesRead = in.read(buf, 0, 0);
224             assertEquals(0, bytesRead, "Base45InputStream.read(buf, 0, 0) returns 0");
225         }
226     }
227 
228     /**
229      * Tests read with null.
230      *
231      * @throws Exception Thrown for some failure scenarios.
232      */
233     @Test
234     void testReadNull() throws Exception {
235         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
236         try (Base45InputStream in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
237             assertThrows(NullPointerException.class, () -> in.read(null, 0, 0));
238         }
239     }
240 
241     /**
242      * Tests read throwing IndexOutOfBoundsException
243      *
244      * @throws Exception Thrown for some failure scenarios.
245      */
246     @Test
247     void testReadOutOfBounds() throws Exception {
248         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
249         final byte[] buf = new byte[1024];
250         try (Base45InputStream in = Base45InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
251             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, -1, 0), "Base45InputStream.read(buf, -1, 0)");
252             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, 0, -1), "Base45InputStream.read(buf, 0, -1)");
253             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length + 1, 0), "Base45InputStream.read(buf, buf.length + 1, 0)");
254             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length - 1, 2), "Base45InputStream.read(buf, buf.length - 1, 2)");
255         }
256     }
257 
258     /**
259      * Tests skipping number of characters larger than the internal buffer.
260      *
261      * @throws Throwable Thrown for some failure scenarios.
262      */
263     @Test
264     void testSkipBig() throws Throwable {
265         final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
266         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
267         try (Base45InputStream b45stream = new Base45InputStream(ins)) {
268             assertEquals(3, b45stream.skip(1024));
269             // End of stream reached
270             assertEquals(-1, b45stream.read());
271             assertEquals(-1, b45stream.read());
272         }
273     }
274 
275     /**
276      * Tests skipping as a noop
277      *
278      * @throws Throwable Thrown for some failure scenarios.
279      */
280     @Test
281     void testSkipNone() throws Throwable {
282         final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
283         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
284         try (Base45InputStream b45stream = new Base45InputStream(ins)) {
285             final byte[] actualBytes = new byte[3];
286             assertEquals(0, b45stream.skip(0));
287             b45stream.read(actualBytes, 0, actualBytes.length);
288             assertArrayEquals(actualBytes, new byte[] { 102, 111, 111 });
289             // End of stream reached
290             assertEquals(-1, b45stream.read());
291         }
292     }
293 
294     /**
295      * Tests skipping past the end of a stream.
296      *
297      * @throws Throwable Thrown for some failure scenarios.
298      */
299     @Test
300     void testSkipPastEnd() throws Throwable {
301         final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
302         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
303         try (Base45InputStream b45stream = new Base45InputStream(ins)) {
304             // skip correctly decoded characters
305             assertEquals(3, b45stream.skip(10));
306             // End of stream reached
307             assertEquals(-1, b45stream.read());
308             assertEquals(-1, b45stream.read());
309         }
310     }
311 
312     /**
313      * Tests skipping to the end of a stream.
314      *
315      * @throws Throwable Thrown for some failure scenarios.
316      */
317     @Test
318     void testSkipToEnd() throws Throwable {
319         final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
320         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
321         try (Base45InputStream b45stream = new Base45InputStream(ins)) {
322             // skip correctly decoded characters
323             assertEquals(3, b45stream.skip(3));
324             assertEquals(-1, b45stream.read());
325         }
326     }
327 
328     /**
329      * Tests if negative arguments to skip are handled correctly.
330      *
331      * @throws Throwable Thrown for some failure scenarios.
332      */
333     @Test
334     void testSkipWrongArgument() throws Throwable {
335         final String encoded = new String(new Base45().encode(StringUtils.getBytesUtf8("foo")));
336         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
337         try (Base45InputStream b45stream = new Base45InputStream(ins)) {
338             assertThrows(IllegalArgumentException.class, () -> b45stream.skip(-1));
339         }
340     }
341 }