1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.ByteArrayOutputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33
34 import org.apache.commons.codec.CodecPolicy;
35 import org.junit.jupiter.api.Test;
36 import org.junit.jupiter.api.io.TempDir;
37
38
39
40
41 class Base64OutputStreamTest extends AbstractBaseNOutputStreamTest {
42
43 private static final byte[] CR_LF = {(byte) '\r', (byte) '\n'};
44
45 private static final byte[] LF = {(byte) '\n'};
46
47 private static final String STRING_FIXTURE = "Hello World";
48
49 @Override
50 OutputStream newOutputStream() {
51 return new Base64OutputStream(new ByteArrayOutputStream());
52 }
53
54 private void testBase64EmptyOutputStream(final int chunkSize) throws Exception {
55 final byte[] emptyEncoded = {};
56 final byte[] emptyDecoded = {};
57 testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
58 testByChunk(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
59 }
60
61
62
63
64
65
66
67 @Test
68 void testBase64EmptyOutputStreamMimeChunkSize() throws Exception {
69 testBase64EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
70 }
71
72
73
74
75
76
77
78 @Test
79 void testBase64EmptyOutputStreamPemChunkSize() throws Exception {
80 testBase64EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
81 }
82
83
84
85
86
87
88
89 @Test
90 void testBase64OutputStreamByChunk() throws Exception {
91
92 byte[] encoded = StringUtils.getBytesUtf8("SGVsbG8gV29ybGQ=\r\n");
93 byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
94 testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
95
96
97 encoded = StringUtils.getBytesUtf8("AA==\r\n");
98 decoded = new byte[]{(byte) 0};
99 testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
100
101
102 encoded = StringUtils.getBytesUtf8(Base64TestData.ENCODED_64_CHARS_PER_LINE);
103 decoded = BaseNTestData.DECODED;
104 testByChunk(encoded, decoded, BaseNCodec.PEM_CHUNK_SIZE, LF);
105
106
107 final String singleLine = Base64TestData.ENCODED_64_CHARS_PER_LINE.replace("\n", "");
108 encoded = StringUtils.getBytesUtf8(singleLine);
109 decoded = BaseNTestData.DECODED;
110 testByChunk(encoded, decoded, 0, LF);
111
112
113 final BaseNCodec codec = new Base64(0, null, false);
114 for (int i = 0; i <= 150; i++) {
115 final byte[][] randomData = BaseNTestData.randomData(codec, i);
116 encoded = randomData[1];
117 decoded = randomData[0];
118 testByChunk(encoded, decoded, 0, LF);
119 }
120 }
121
122
123
124
125
126
127
128 @Test
129 void testBase64OutputStreamByteByByte() throws Exception {
130
131 byte[] encoded = StringUtils.getBytesUtf8("SGVsbG8gV29ybGQ=\r\n");
132 byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
133 testByteByByte(encoded, decoded, 76, CR_LF);
134
135
136 encoded = StringUtils.getBytesUtf8("AA==\r\n");
137 decoded = new byte[]{(byte) 0};
138 testByteByByte(encoded, decoded, 76, CR_LF);
139
140
141 encoded = StringUtils.getBytesUtf8(Base64TestData.ENCODED_64_CHARS_PER_LINE);
142 decoded = BaseNTestData.DECODED;
143 testByteByByte(encoded, decoded, 64, LF);
144
145
146 final String singleLine = Base64TestData.ENCODED_64_CHARS_PER_LINE.replace("\n", "");
147 encoded = StringUtils.getBytesUtf8(singleLine);
148 decoded = BaseNTestData.DECODED;
149 testByteByByte(encoded, decoded, 0, LF);
150
151
152 final BaseNCodec codec = new Base64(0, null, false);
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(Base64OutputStream.builder().getBaseNCodec());
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184 private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
185
186
187 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
188 try (OutputStream out = new Base64OutputStream(byteOut, true, chunkSize, separator)) {
189 out.write(decoded);
190 }
191 byte[] output = byteOut.toByteArray();
192 assertArrayEquals(encoded, output, "Streaming chunked base64 encode");
193
194
195 byteOut = new ByteArrayOutputStream();
196 try (OutputStream out = new Base64OutputStream(byteOut, false)) {
197 out.write(encoded);
198 }
199 output = byteOut.toByteArray();
200 assertArrayEquals(decoded, output, "Streaming chunked base64 decode");
201
202
203 byteOut = new ByteArrayOutputStream();
204 OutputStream out = byteOut;
205 for (int i = 0; i < 10; i++) {
206 out = new Base64OutputStream(out, false);
207 out = new Base64OutputStream(out, true, chunkSize, separator);
208 }
209 out.write(decoded);
210 out.close();
211 output = byteOut.toByteArray();
212
213 assertArrayEquals(decoded, output, "Streaming chunked base64 wrap-wrap-wrap!");
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234 private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
235
236
237 ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
238 try (OutputStream out = new Base64OutputStream(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 base64 encode");
245
246
247 byteOut = new ByteArrayOutputStream();
248 try (OutputStream out = new Base64OutputStream(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 base64 decode");
255
256
257 byteOut = new ByteArrayOutputStream();
258 try (OutputStream out = new Base64OutputStream(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() base64 decode");
266
267
268 byteOut = new ByteArrayOutputStream();
269 OutputStream out = byteOut;
270 for (int i = 0; i < 10; i++) {
271 out = new Base64OutputStream(out, false);
272 out = new Base64OutputStream(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 base64 wrap-wrap-wrap!");
281 }
282
283
284
285
286 @Test
287 void testCloseIdempotentCreateTempFile() throws Exception {
288 final Path tmp = Files.createTempFile("codec-test", ".bin");
289 try {
290 final OutputStream out = new Base64OutputStream(new FileOutputStream(tmp.toFile()));
291 out.close();
292 out.close();
293 } finally {
294 Files.deleteIfExists(tmp);
295 }
296 }
297
298
299
300
301 @Test
302 void testCloseIdempotentFileOutputStream(@TempDir final Path tempDir) throws Exception {
303 final Path tmp = Files.createFile(tempDir.resolve(getClass().getSimpleName() + ".tmp"));
304 try (OutputStream out = new Base64OutputStream(new FileOutputStream(tmp.toFile()))) {
305 out.close();
306 }
307 }
308
309
310
311
312
313
314
315 @Test
316 void testCodec98NPE() throws Exception {
317 final byte[] codec98 = StringUtils.getBytesUtf8(Base64TestData.CODEC_98_NPE);
318 final byte[] codec98_1024 = new byte[1024];
319 System.arraycopy(codec98, 0, codec98_1024, 0, codec98.length);
320 final ByteArrayOutputStream data = new ByteArrayOutputStream(1024);
321 try (Base64OutputStream stream = new Base64OutputStream(data, false)) {
322 stream.write(codec98_1024, 0, 1024);
323 }
324
325 final byte[] decodedBytes = data.toByteArray();
326 final String decoded = StringUtils.newStringUtf8(decodedBytes);
327 assertEquals(Base64TestData.CODEC_98_NPE_DECODED, decoded, "codec-98 NPE Base64OutputStream");
328 }
329
330
331
332
333
334
335
336 @Test
337 void testStrictDecoding() throws Exception {
338 for (final String impossibleStr : Base64Test.BASE64_IMPOSSIBLE_CASES) {
339 final byte[] impossibleEncoded = StringUtils.getBytesUtf8(impossibleStr);
340 ByteArrayOutputStream bout = new ByteArrayOutputStream();
341 try (Base64OutputStream out = new Base64OutputStream(bout, false)) {
342
343 assertFalse(out.isStrictDecoding());
344 out.write(impossibleEncoded);
345 }
346 assertTrue(bout.size() > 0);
347
348 bout = new ByteArrayOutputStream();
349 try (Base64OutputStream out = new Base64OutputStream(bout, false, 0, null, CodecPolicy.STRICT)) {
350
351
352 final IOException ioe = assertThrows(IOException.class, () -> {
353 out.write(impossibleEncoded);
354 out.close();
355 });
356 assertTrue(ioe.getCause() instanceof IllegalArgumentException);
357
358 }
359 try (Base64OutputStream out = Base64OutputStream.builder()
360 .setOutputStream(bout).setEncode(false)
361 .setBaseNCodec(Base64.builder().setLineLength(0).setLineSeparator(null).setDecodingPolicy(CodecPolicy.STRICT).get())
362 .get()) {
363 assertTrue(out.isStrictDecoding());
364 final IOException ioe = assertThrows(IOException.class, () -> {
365 out.write(impossibleEncoded);
366 out.close();
367 });
368 assertTrue(ioe.getCause() instanceof IllegalArgumentException);
369 }
370 try (Base64OutputStream out = Base64OutputStream.builder()
371 .setOutputStream(bout).setEncode(false)
372 .setBaseNCodec(Base64.builder().setDecodingPolicy(CodecPolicy.STRICT).get())
373 .get()) {
374
375
376 final IOException ioe = assertThrows(IOException.class, () -> {
377 out.write(impossibleEncoded);
378 out.close();
379 });
380 assertTrue(ioe.getCause() instanceof IllegalArgumentException);
381 }
382 }
383 }
384
385
386
387
388
389
390
391 @Test
392 void testWriteOutOfBounds() throws Exception {
393 final byte[] buf = new byte[1024];
394 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
395 try (Base64OutputStream out = new Base64OutputStream(bout)) {
396 assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base64OutputStream.write(buf, -1, 1)");
397 assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base64OutputStream.write(buf, 1, -1)");
398 assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base64OutputStream.write(buf, buf.length + 1, 0)");
399 assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base64OutputStream.write(buf, buf.length - 1, 2)");
400 }
401 }
402
403
404
405
406
407
408
409 @Test
410 void testWriteToNullCoverage() throws Exception {
411 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
412 try (Base64OutputStream out = new Base64OutputStream(bout)) {
413 assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
414 }
415 }
416 }