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