1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 package libcore.java.util;
18 
19 import junit.framework.TestCase;
20 
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.nio.ByteBuffer;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Base64;
30 import java.util.Base64.Decoder;
31 import java.util.Base64.Encoder;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Random;
37 import java.util.Set;
38 
39 import libcore.util.HexEncoding;
40 
41 import static java.nio.charset.StandardCharsets.US_ASCII;
42 import static java.util.Arrays.copyOfRange;
43 
44 public class Base64Test extends TestCase {
45 
46     /**
47      * The base 64 alphabet from RFC 4648 Table 1.
48      */
49     private static final Set<Character> TABLE_1 =
50             Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
51                     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
52                     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
53                     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
54                     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
55                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
56             )));
57 
58     /**
59      * The "URL and Filename safe" Base 64 Alphabet from RFC 4648 Table 2.
60      */
61     private static final Set<Character> TABLE_2 =
62             Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
63                     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
64                     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
65                     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
66                     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
67                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
68             )));
69 
testAlphabet_plain()70     public void testAlphabet_plain() {
71         checkAlphabet(TABLE_1, "", Base64.getEncoder());
72     }
73 
testAlphabet_mime()74     public void testAlphabet_mime() {
75         checkAlphabet(TABLE_1, "\r\n", Base64.getMimeEncoder());
76     }
77 
testAlphabet_url()78     public void testAlphabet_url() {
79         checkAlphabet(TABLE_2, "", Base64.getUrlEncoder());
80     }
81 
checkAlphabet(Set<Character> expectedAlphabet, String lineSeparator, Encoder encoder)82     private static void checkAlphabet(Set<Character> expectedAlphabet, String lineSeparator,
83             Encoder encoder) {
84         assertEquals("Base64 alphabet size must be 64 characters", 64, expectedAlphabet.size());
85         byte[] bytes = new byte[256];
86         for (int i = 0; i < 256; i++) {
87             bytes[i] = (byte) i;
88         }
89         Set<Character> actualAlphabet = new HashSet<>();
90 
91         byte[] encodedBytes = encoder.encode(bytes);
92         // ignore the padding
93         int endIndex = encodedBytes.length;
94         while (endIndex > 0 && encodedBytes[endIndex - 1] == '=') {
95             endIndex--;
96         }
97         for (byte b : Arrays.copyOfRange(encodedBytes, 0, endIndex)) {
98             char c = (char) b;
99             actualAlphabet.add(c);
100         }
101         for (char c : lineSeparator.toCharArray()) {
102             assertTrue(actualAlphabet.remove(c));
103         }
104         assertEquals(expectedAlphabet, actualAlphabet);
105     }
106 
107     /**
108      * Checks decoding of bytes containing a value outside of the allowed
109      * {@link #TABLE_1 "basic" alphabet}.
110      */
testDecoder_extraChars_basic()111     public void testDecoder_extraChars_basic() throws Exception {
112         Decoder basicDecoder = Base64.getDecoder(); // uses Table 1
113         // Check failure cases common to both RFC4648 Table 1 and Table 2 decoding.
114         checkDecoder_extraChars_common(basicDecoder);
115 
116         // Tests characters that are part of RFC4848 Table 2 but not Table 1.
117         assertDecodeThrowsIAe(basicDecoder, "_aGVsbG8sIHdvcmx");
118         assertDecodeThrowsIAe(basicDecoder, "aGV_sbG8sIHdvcmx");
119         assertDecodeThrowsIAe(basicDecoder, "aGVsbG8sIHdvcmx_");
120     }
121 
122     /**
123      * Checks decoding of bytes containing a value outside of the allowed
124      * {@link #TABLE_2 url alphabet}.
125      */
testDecoder_extraChars_url()126     public void testDecoder_extraChars_url() throws Exception {
127         Decoder urlDecoder = Base64.getUrlDecoder(); // uses Table 2
128         // Check failure cases common to both RFC4648 table 1 and table 2 decoding.
129         checkDecoder_extraChars_common(urlDecoder);
130 
131         // Tests characters that are part of RFC4848 Table 1 but not Table 2.
132         assertDecodeThrowsIAe(urlDecoder, "/aGVsbG8sIHdvcmx");
133         assertDecodeThrowsIAe(urlDecoder, "aGV/sbG8sIHdvcmx");
134         assertDecodeThrowsIAe(urlDecoder, "aGVsbG8sIHdvcmx/");
135     }
136 
137     /**
138      * Checks characters that are bad both in RFC4648 {@link #TABLE_1} and
139      * in {@link #TABLE_2} based decoding.
140      */
checkDecoder_extraChars_common(Decoder decoder)141     private static void checkDecoder_extraChars_common(Decoder decoder) throws Exception {
142         // Characters outside alphabet before padding.
143         assertDecodeThrowsIAe(decoder, " aGVsbG8sIHdvcmx");
144         assertDecodeThrowsIAe(decoder, "aGV sbG8sIHdvcmx");
145         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx ");
146         assertDecodeThrowsIAe(decoder, "*aGVsbG8sIHdvcmx");
147         assertDecodeThrowsIAe(decoder, "aGV*sbG8sIHdvcmx");
148         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx*");
149         assertDecodeThrowsIAe(decoder, "\r\naGVsbG8sIHdvcmx");
150         assertDecodeThrowsIAe(decoder, "aGV\r\nsbG8sIHdvcmx");
151         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx\r\n");
152         assertDecodeThrowsIAe(decoder, "\naGVsbG8sIHdvcmx");
153         assertDecodeThrowsIAe(decoder, "aGV\nsbG8sIHdvcmx");
154         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx\n");
155 
156         // padding 0
157         assertEquals("hello, world", decodeToAscii(decoder, "aGVsbG8sIHdvcmxk"));
158         // Extra padding
159         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk=");
160         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk==");
161         // Characters outside alphabet intermixed with (too much) padding.
162         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk =");
163         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk = = ");
164 
165         // padding 1
166         assertEquals("hello, world?!", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkPyE="));
167         // Missing padding
168         assertEquals("hello, world?!", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkPyE"));
169         // Characters outside alphabet before padding.
170         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE =");
171         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE*=");
172         // Trailing characters, otherwise valid.
173         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE= ");
174         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=*");
175         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=X");
176         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XY");
177         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XYZ");
178         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XYZA");
179         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=\n");
180         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=\r\n");
181         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE= ");
182         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE==");
183         // Characters outside alphabet intermixed with (too much) padding.
184         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE ==");
185         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE = = ");
186 
187         // padding 2
188         assertEquals("hello, world.", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkLg=="));
189         // Missing padding
190         assertEquals("hello, world.", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkLg"));
191         // Partially missing padding
192         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=");
193         // Characters outside alphabet before padding.
194         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg ==");
195         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg*==");
196         // Trailing characters, otherwise valid.
197         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg== ");
198         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==*");
199         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==X");
200         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XY");
201         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XYZ");
202         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XYZA");
203         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==\n");
204         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==\r\n");
205         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg== ");
206         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg===");
207         // Characters outside alphabet inside padding.
208         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg= =");
209         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=*=");
210         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=\r\n=");
211         // Characters inside alphabet inside padding.
212         assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=X=");
213     }
214 
testDecoder_extraChars_mime()215     public void testDecoder_extraChars_mime() throws Exception {
216         Decoder mimeDecoder = Base64.getMimeDecoder();
217 
218         // Characters outside alphabet before padding.
219         assertEquals("hello, world", decodeToAscii(mimeDecoder, " aGVsbG8sIHdvcmxk"));
220         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV sbG8sIHdvcmxk"));
221         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk "));
222         assertEquals("hello, world", decodeToAscii(mimeDecoder, "_aGVsbG8sIHdvcmxk"));
223         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV_sbG8sIHdvcmxk"));
224         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk_"));
225         assertEquals("hello, world", decodeToAscii(mimeDecoder, "*aGVsbG8sIHdvcmxk"));
226         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV*sbG8sIHdvcmxk"));
227         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk*"));
228         assertEquals("hello, world", decodeToAscii(mimeDecoder, "\r\naGVsbG8sIHdvcmxk"));
229         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV\r\nsbG8sIHdvcmxk"));
230         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk\r\n"));
231         assertEquals("hello, world", decodeToAscii(mimeDecoder, "\naGVsbG8sIHdvcmxk"));
232         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV\nsbG8sIHdvcmxk"));
233         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk\n"));
234 
235         // padding 0
236         assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk"));
237         // Extra padding
238         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk=");
239         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk==");
240         // Characters outside alphabet intermixed with (too much) padding.
241         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk =");
242         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk = = ");
243 
244         // padding 1
245         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE="));
246         // Missing padding
247         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE"));
248         // Characters outside alphabet before padding.
249         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE ="));
250         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE*="));
251         // Trailing characters, otherwise valid.
252         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE= "));
253         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=*"));
254         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=X");
255         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XY");
256         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XYZ");
257         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XYZA");
258         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=\n"));
259         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=\r\n"));
260         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE= "));
261         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=="));
262         // Characters outside alphabet intermixed with (too much) padding.
263         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE =="));
264         assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE = = "));
265 
266         // padding 2
267         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg=="));
268         // Missing padding
269         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg"));
270         // Partially missing padding
271         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=");
272         // Characters outside alphabet before padding.
273         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg =="));
274         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg*=="));
275         // Trailing characters, otherwise valid.
276         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg== "));
277         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==*"));
278         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==X");
279         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XY");
280         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XYZ");
281         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XYZA");
282         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==\n"));
283         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==\r\n"));
284         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg== "));
285         assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==="));
286 
287         // Characters outside alphabet inside padding are not allowed by the MIME decoder.
288         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg= =");
289         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=*=");
290         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=\r\n=");
291 
292         // Characters inside alphabet inside padding.
293         assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=X=");
294     }
295 
testDecoder_nonPrintableBytes_basic()296     public void testDecoder_nonPrintableBytes_basic() throws Exception {
297         checkDecoder_nonPrintableBytes_table1(Base64.getDecoder());
298     }
299 
testDecoder_nonPrintableBytes_mime()300     public void testDecoder_nonPrintableBytes_mime() throws Exception {
301         checkDecoder_nonPrintableBytes_table1(Base64.getMimeDecoder());
302     }
303 
304     /**
305      * Check decoding sample non-ASCII byte[] values from a {@link #TABLE_1}
306      * encoded String.
307      */
checkDecoder_nonPrintableBytes_table1(Decoder decoder)308     private static void checkDecoder_nonPrintableBytes_table1(Decoder decoder) throws Exception {
309         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 0, decoder.decode(""));
310         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 1, decoder.decode("/w=="));
311         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 2, decoder.decode("/+4="));
312         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 3, decoder.decode("/+7d"));
313         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 4, decoder.decode("/+7dzA=="));
314         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 5, decoder.decode("/+7dzLs="));
315         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 6, decoder.decode("/+7dzLuq"));
316         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 7, decoder.decode("/+7dzLuqmQ=="));
317         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 8, decoder.decode("/+7dzLuqmYg="));
318     }
319 
320     /**
321      * Check decoding sample non-ASCII byte[] values from a {@link #TABLE_2}
322      * (url safe) encoded String.
323      */
testDecoder_nonPrintableBytes_url()324     public void testDecoder_nonPrintableBytes_url() throws Exception {
325         Decoder decoder = Base64.getUrlDecoder();
326         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 0, decoder.decode(""));
327         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 1, decoder.decode("_w=="));
328         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 2, decoder.decode("_-4="));
329         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 3, decoder.decode("_-7d"));
330         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 4, decoder.decode("_-7dzA=="));
331         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 5, decoder.decode("_-7dzLs="));
332         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 6, decoder.decode("_-7dzLuq"));
333         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 7, decoder.decode("_-7dzLuqmQ=="));
334         assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 8, decoder.decode("_-7dzLuqmYg="));
335     }
336 
337     private static final byte[] SAMPLE_NON_ASCII_BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
338             (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
339             (byte) 0x99, (byte) 0x88, (byte) 0x77 };
340 
testDecoder_closedStream()341     public void testDecoder_closedStream() {
342         try {
343             closedDecodeStream().available();
344             fail("Should have thrown");
345         } catch (IOException expected) {
346         }
347         try {
348             closedDecodeStream().read();
349             fail("Should have thrown");
350         } catch (IOException expected) {
351         }
352         try {
353             closedDecodeStream().read(new byte[23]);
354             fail("Should have thrown");
355         } catch (IOException expected) {
356         }
357 
358         try {
359             closedDecodeStream().read(new byte[23], 0, 1);
360             fail("Should have thrown");
361         } catch (IOException expected) {
362         }
363     }
364 
closedDecodeStream()365     private static InputStream closedDecodeStream() {
366         InputStream result = Base64.getDecoder().wrap(new ByteArrayInputStream(new byte[0]));
367         try {
368             result.close();
369         } catch (IOException e) {
370             fail(e.getMessage());
371         }
372         return result;
373     }
374 
375     /**
376      * Tests {@link Decoder#decode(byte[], byte[])} for correctness as well as
377      * for consistency with other methods tested elsewhere.
378      */
testDecoder_decodeArrayToArray()379     public void testDecoder_decodeArrayToArray() {
380         Decoder decoder = Base64.getDecoder();
381 
382         // Empty input
383         assertEquals(0, decoder.decode(new byte[0], new byte[0]));
384 
385         // Test data for non-empty input
386         String inputString = "YWJjZWZnaGk=";
387         byte[] input = inputString.getBytes(US_ASCII);
388         String expectedString = "abcefghi";
389         byte[] decodedBytes = expectedString.getBytes(US_ASCII);
390         // check test data consistency with other methods that are tested elsewhere
391         assertRoundTrip(Base64.getEncoder(), decoder, inputString, decodedBytes);
392 
393         // Non-empty input: output array too short
394         byte[] tooShort = new byte[decodedBytes.length - 1];
395         try {
396             decoder.decode(input, tooShort);
397             fail();
398         } catch (IllegalArgumentException expected) {
399         }
400 
401         // Non-empty input: output array longer than required
402         byte[] tooLong = new byte[decodedBytes.length + 1];
403         int tooLongBytesDecoded = decoder.decode(input, tooLong);
404         assertEquals(decodedBytes.length, tooLongBytesDecoded);
405         assertEquals(0, tooLong[tooLong.length - 1]);
406         assertArrayPrefixEquals(tooLong, decodedBytes.length, decodedBytes);
407 
408         // Non-empty input: output array has exact minimum required size
409         byte[] justRight = new byte[decodedBytes.length];
410         int justRightBytesDecoded = decoder.decode(input, justRight);
411         assertEquals(decodedBytes.length, justRightBytesDecoded);
412         assertArrayEquals(decodedBytes, justRight);
413 
414     }
415 
testDecoder_decodeByteBuffer()416     public void testDecoder_decodeByteBuffer() {
417         Decoder decoder = Base64.getDecoder();
418 
419         byte[] emptyByteArray = new byte[0];
420         ByteBuffer emptyByteBuffer = ByteBuffer.wrap(emptyByteArray);
421         ByteBuffer emptyDecodedBuffer = decoder.decode(emptyByteBuffer);
422         assertEquals(emptyByteBuffer, emptyDecodedBuffer);
423         assertNotSame(emptyByteArray, emptyDecodedBuffer);
424 
425         // Test the two types of byte buffer.
426         String inputString = "YWJjZWZnaGk=";
427         byte[] input = inputString.getBytes(US_ASCII);
428         String expectedString = "abcefghi";
429         byte[] expectedBytes = expectedString.getBytes(US_ASCII);
430 
431         ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
432         inputBuffer.put(input);
433         inputBuffer.position(0);
434         checkDecoder_decodeByteBuffer(decoder, inputBuffer, expectedBytes);
435 
436         inputBuffer = ByteBuffer.allocateDirect(input.length);
437         inputBuffer.put(input);
438         inputBuffer.position(0);
439         checkDecoder_decodeByteBuffer(decoder, inputBuffer, expectedBytes);
440     }
441 
checkDecoder_decodeByteBuffer( Decoder decoder, ByteBuffer inputBuffer, byte[] expectedBytes)442     private static void checkDecoder_decodeByteBuffer(
443             Decoder decoder, ByteBuffer inputBuffer, byte[] expectedBytes) {
444         assertEquals(0, inputBuffer.position());
445         assertEquals(inputBuffer.remaining(), inputBuffer.limit());
446         int inputLength = inputBuffer.remaining();
447 
448         ByteBuffer decodedBuffer = decoder.decode(inputBuffer);
449 
450         assertEquals(inputLength, inputBuffer.position());
451         assertEquals(0, inputBuffer.remaining());
452         assertEquals(inputLength, inputBuffer.limit());
453         assertEquals(0, decodedBuffer.position());
454         assertEquals(expectedBytes.length, decodedBuffer.remaining());
455         assertEquals(expectedBytes.length, decodedBuffer.limit());
456     }
457 
testDecoder_decodeByteBuffer_invalidData()458     public void testDecoder_decodeByteBuffer_invalidData() {
459         Decoder decoder = Base64.getDecoder();
460 
461         // Test the two types of byte buffer.
462         String inputString = "AAAA AAAA";
463         byte[] input = inputString.getBytes(US_ASCII);
464 
465         ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
466         inputBuffer.put(input);
467         inputBuffer.position(0);
468         checkDecoder_decodeByteBuffer_invalidData(decoder, inputBuffer);
469 
470         inputBuffer = ByteBuffer.allocateDirect(input.length);
471         inputBuffer.put(input);
472         inputBuffer.position(0);
473         checkDecoder_decodeByteBuffer_invalidData(decoder, inputBuffer);
474     }
475 
checkDecoder_decodeByteBuffer_invalidData( Decoder decoder, ByteBuffer inputBuffer)476     private static void checkDecoder_decodeByteBuffer_invalidData(
477             Decoder decoder, ByteBuffer inputBuffer) {
478         assertEquals(0, inputBuffer.position());
479         assertEquals(inputBuffer.remaining(), inputBuffer.limit());
480         int limit = inputBuffer.limit();
481 
482         try {
483             decoder.decode(inputBuffer);
484             fail();
485         } catch (IllegalArgumentException expected) {
486         }
487 
488         assertEquals(0, inputBuffer.position());
489         assertEquals(limit, inputBuffer.remaining());
490         assertEquals(limit, inputBuffer.limit());
491     }
492 
testDecoder_nullArgs()493     public void testDecoder_nullArgs() {
494         checkDecoder_nullArgs(Base64.getDecoder());
495         checkDecoder_nullArgs(Base64.getMimeDecoder());
496         checkDecoder_nullArgs(Base64.getUrlDecoder());
497     }
498 
checkDecoder_nullArgs(Decoder decoder)499     private static void checkDecoder_nullArgs(Decoder decoder) {
500         assertThrowsNpe(() -> decoder.decode((byte[]) null));
501         assertThrowsNpe(() -> decoder.decode((String) null));
502         assertThrowsNpe(() -> decoder.decode(null, null));
503         assertThrowsNpe(() -> decoder.decode((ByteBuffer) null));
504         assertThrowsNpe(() -> decoder.wrap(null));
505     }
506 
testEncoder_nullArgs()507     public void testEncoder_nullArgs() {
508         checkEncoder_nullArgs(Base64.getEncoder());
509         checkEncoder_nullArgs(Base64.getMimeEncoder());
510         checkEncoder_nullArgs(Base64.getUrlEncoder());
511         checkEncoder_nullArgs(Base64.getMimeEncoder(20, new byte[] { '*' }));
512         checkEncoder_nullArgs(Base64.getEncoder().withoutPadding());
513         checkEncoder_nullArgs(Base64.getMimeEncoder().withoutPadding());
514         checkEncoder_nullArgs(Base64.getUrlEncoder().withoutPadding());
515         checkEncoder_nullArgs(Base64.getMimeEncoder(20, new byte[] { '*' }).withoutPadding());
516 
517     }
518 
checkEncoder_nullArgs(Encoder encoder)519     private static void checkEncoder_nullArgs(Encoder encoder) {
520         assertThrowsNpe(() -> encoder.encode((byte[]) null));
521         assertThrowsNpe(() -> encoder.encodeToString(null));
522         assertThrowsNpe(() -> encoder.encode(null, null));
523         assertThrowsNpe(() -> encoder.encode((ByteBuffer) null));
524         assertThrowsNpe(() -> encoder.wrap(null));
525     }
526 
testEncoder_nonPrintableBytes()527     public void testEncoder_nonPrintableBytes() throws Exception {
528         Encoder encoder = Base64.getUrlEncoder();
529         assertEquals("", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 0)));
530         assertEquals("_w==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 1)));
531         assertEquals("_-4=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 2)));
532         assertEquals("_-7d", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 3)));
533         assertEquals("_-7dzA==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 4)));
534         assertEquals("_-7dzLs=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 5)));
535         assertEquals("_-7dzLuq", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 6)));
536         assertEquals("_-7dzLuqmQ==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 7)));
537         assertEquals("_-7dzLuqmYg=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 8)));
538     }
539 
testEncoder_lineLength()540     public void testEncoder_lineLength() throws Exception {
541         String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
542         String in_57 = in_56 + "e";
543         String in_58 = in_56 + "ef";
544         String in_59 = in_56 + "efg";
545         String in_60 = in_56 + "efgh";
546         String in_61 = in_56 + "efghi";
547 
548         String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
549         String out_56 = prefix + "Y2Q=";
550         String out_57 = prefix + "Y2Rl";
551         String out_58 = prefix + "Y2Rl\r\nZg==";
552         String out_59 = prefix + "Y2Rl\r\nZmc=";
553         String out_60 = prefix + "Y2Rl\r\nZmdo";
554         String out_61 = prefix + "Y2Rl\r\nZmdoaQ==";
555 
556         Encoder encoder = Base64.getMimeEncoder();
557         Decoder decoder = Base64.getMimeDecoder();
558         assertEquals("", encodeFromAscii(encoder, decoder, ""));
559         assertEquals(out_56, encodeFromAscii(encoder, decoder, in_56));
560         assertEquals(out_57, encodeFromAscii(encoder, decoder, in_57));
561         assertEquals(out_58, encodeFromAscii(encoder, decoder, in_58));
562         assertEquals(out_59, encodeFromAscii(encoder, decoder, in_59));
563         assertEquals(out_60, encodeFromAscii(encoder, decoder, in_60));
564         assertEquals(out_61, encodeFromAscii(encoder, decoder, in_61));
565 
566         encoder = Base64.getUrlEncoder();
567         decoder = Base64.getUrlDecoder();
568         assertEquals(out_56.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_56));
569         assertEquals(out_57.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_57));
570         assertEquals(out_58.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_58));
571         assertEquals(out_59.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_59));
572         assertEquals(out_60.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_60));
573         assertEquals(out_61.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_61));
574     }
575 
testGetMimeEncoder_invalidLineSeparator()576     public void testGetMimeEncoder_invalidLineSeparator() {
577         byte[] invalidLineSeparator = { 'A' };
578         try {
579             Base64.getMimeEncoder(20, invalidLineSeparator);
580             fail();
581         } catch (IllegalArgumentException expected) {
582         }
583 
584         try {
585             Base64.getMimeEncoder(0, invalidLineSeparator);
586             fail();
587         } catch (IllegalArgumentException expected) {
588         }
589 
590         try {
591             Base64.getMimeEncoder(20, null);
592             fail();
593         } catch (NullPointerException expected) {
594         }
595 
596         try {
597             Base64.getMimeEncoder(0, null);
598             fail();
599         } catch (NullPointerException expected) {
600         }
601     }
602 
testEncoder_closedStream()603     public void testEncoder_closedStream() {
604         try {
605             closedEncodeStream().write(100);
606             fail("Should have thrown");
607         } catch (IOException expected) {
608         }
609         try {
610             closedEncodeStream().write(new byte[100]);
611             fail("Should have thrown");
612         } catch (IOException expected) {
613         }
614 
615         try {
616             closedEncodeStream().write(new byte[100], 0, 1);
617             fail("Should have thrown");
618         } catch (IOException expected) {
619         }
620     }
621 
closedEncodeStream()622     private static OutputStream closedEncodeStream() {
623         OutputStream result = Base64.getEncoder().wrap(new ByteArrayOutputStream());
624         try {
625             result.close();
626         } catch (IOException e) {
627             fail(e.getMessage());
628         }
629         return result;
630     }
631 
632 
633     /**
634      * Tests {@link Decoder#decode(byte[], byte[])} for correctness.
635      */
testEncoder_encodeArrayToArray()636     public void testEncoder_encodeArrayToArray() {
637         Encoder encoder = Base64.getEncoder();
638 
639         // Empty input
640         assertEquals(0, encoder.encode(new byte[0], new byte[0]));
641 
642         // Test data for non-empty input
643         byte[] input = "abcefghi".getBytes(US_ASCII);
644         String expectedString = "YWJjZWZnaGk=";
645         byte[] encodedBytes = expectedString.getBytes(US_ASCII);
646 
647         // Non-empty input: output array too short
648         byte[] tooShort = new byte[encodedBytes.length - 1];
649         try {
650             encoder.encode(input, tooShort);
651             fail();
652         } catch (IllegalArgumentException expected) {
653         }
654 
655         // Non-empty input: output array longer than required
656         byte[] tooLong = new byte[encodedBytes.length + 1];
657         int tooLongBytesEncoded = encoder.encode(input, tooLong);
658         assertEquals(encodedBytes.length, tooLongBytesEncoded);
659         assertEquals(0, tooLong[tooLong.length - 1]);
660         assertArrayPrefixEquals(tooLong, encodedBytes.length, encodedBytes);
661 
662         // Non-empty input: output array has exact minimum required size
663         byte[] justRight = new byte[encodedBytes.length];
664         int justRightBytesEncoded = encoder.encode(input, justRight);
665         assertEquals(encodedBytes.length, justRightBytesEncoded);
666         assertArrayEquals(encodedBytes, justRight);
667     }
668 
testEncoder_encodeByteBuffer()669     public void testEncoder_encodeByteBuffer() {
670         Encoder encoder = Base64.getEncoder();
671 
672         byte[] emptyByteArray = new byte[0];
673         ByteBuffer emptyByteBuffer = ByteBuffer.wrap(emptyByteArray);
674         ByteBuffer emptyEncodedBuffer = encoder.encode(emptyByteBuffer);
675         assertEquals(emptyByteBuffer, emptyEncodedBuffer);
676         assertNotSame(emptyByteArray, emptyEncodedBuffer);
677 
678         // Test the two types of byte buffer.
679         byte[] input = "abcefghi".getBytes(US_ASCII);
680         String expectedString = "YWJjZWZnaGk=";
681         byte[] expectedBytes = expectedString.getBytes(US_ASCII);
682 
683         ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
684         inputBuffer.put(input);
685         inputBuffer.position(0);
686         testEncoder_encodeByteBuffer(encoder, inputBuffer, expectedBytes);
687 
688         inputBuffer = ByteBuffer.allocateDirect(input.length);
689         inputBuffer.put(input);
690         inputBuffer.position(0);
691         testEncoder_encodeByteBuffer(encoder, inputBuffer, expectedBytes);
692     }
693 
testEncoder_encodeByteBuffer( Encoder encoder, ByteBuffer inputBuffer, byte[] expectedBytes)694     private static void testEncoder_encodeByteBuffer(
695             Encoder encoder, ByteBuffer inputBuffer, byte[] expectedBytes) {
696         assertEquals(0, inputBuffer.position());
697         assertEquals(inputBuffer.remaining(), inputBuffer.limit());
698         int inputLength = inputBuffer.remaining();
699 
700         ByteBuffer encodedBuffer = encoder.encode(inputBuffer);
701 
702         assertEquals(inputLength, inputBuffer.position());
703         assertEquals(0, inputBuffer.remaining());
704         assertEquals(inputLength, inputBuffer.limit());
705         assertEquals(0, encodedBuffer.position());
706         assertEquals(expectedBytes.length, encodedBuffer.remaining());
707         assertEquals(expectedBytes.length, encodedBuffer.limit());
708     }
709 
710     /**
711      * Checks that all encoders/decoders map {@code new byte[0]} to "" and vice versa.
712      */
testRoundTrip_empty()713     public void testRoundTrip_empty() {
714         checkRoundTrip_empty(Base64.getEncoder(), Base64.getDecoder());
715         checkRoundTrip_empty(Base64.getMimeEncoder(), Base64.getMimeDecoder());
716         byte[] sep = new byte[] { '\r', '\n' };
717         checkRoundTrip_empty(Base64.getMimeEncoder(-1, sep), Base64.getMimeDecoder());
718         checkRoundTrip_empty(Base64.getMimeEncoder(20, new byte[0]), Base64.getMimeDecoder());
719         checkRoundTrip_empty(Base64.getMimeEncoder(23, sep), Base64.getMimeDecoder());
720         checkRoundTrip_empty(Base64.getMimeEncoder(76, sep), Base64.getMimeDecoder());
721         checkRoundTrip_empty(Base64.getUrlEncoder(), Base64.getUrlDecoder());
722     }
723 
checkRoundTrip_empty(Encoder encoder, Decoder decoder)724     private static void checkRoundTrip_empty(Encoder encoder, Decoder decoder) {
725         assertRoundTrip(encoder, decoder, "", new byte[0]);
726     }
727 
728     /**
729      * Encoding of byte values 0..255 using the non-URL alphabet.
730      */
731     private static final String ALL_BYTE_VALUES_ENCODED =
732             "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4" +
733                     "OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx" +
734                     "cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq" +
735                     "q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj" +
736                     "5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
737 
testRoundTrip_allBytes_plain()738     public void testRoundTrip_allBytes_plain() {
739         checkRoundTrip_allBytes_singleLine(Base64.getEncoder(), Base64.getDecoder());
740     }
741 
742     /**
743      * Checks that if the lineSeparator is empty or the line length is {@code <= 3}
744      * or larger than the data to be encoded, a single line is returned.
745      */
testRoundTrip_allBytes_mime_singleLine()746     public void testRoundTrip_allBytes_mime_singleLine() {
747         Decoder decoder = Base64.getMimeDecoder();
748         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(76, new byte[0]), decoder);
749 
750         // Line lengths <= 3 mean no wrapping; the separator is ignored in that case.
751         byte[] separator = new byte[] { '*' };
752         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(Integer.MIN_VALUE, separator),
753                 decoder);
754         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(-1, separator), decoder);
755         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(0, separator), decoder);
756         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(1, separator), decoder);
757         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(2, separator), decoder);
758         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(3, separator), decoder);
759 
760         // output fits into the permitted line length
761         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(
762                 ALL_BYTE_VALUES_ENCODED.length(), separator), decoder);
763         checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(Integer.MAX_VALUE, separator),
764                 decoder);
765     }
766 
767     /**
768      * Checks round-trip encoding/decoding for a few simple examples that
769      * should work the same across three Encoder/Decoder pairs: This is
770      * because they only use characters that are in both RFC 4648 Table 1
771      * and Table 2, and are short enough to fit into a single line.
772      */
testRoundTrip_simple_basic()773     public void testRoundTrip_simple_basic() throws Exception {
774         // uses Table 1, never adds linebreaks
775         checkRoundTrip_simple(Base64.getEncoder(), Base64.getDecoder());
776         // uses Table 1, allows 76 chars in a line
777         checkRoundTrip_simple(Base64.getMimeEncoder(), Base64.getMimeDecoder());
778         // uses Table 2, never adds linebreaks
779         checkRoundTrip_simple(Base64.getUrlEncoder(), Base64.getUrlDecoder());
780     }
781 
checkRoundTrip_simple(Encoder encoder, Decoder decoder)782     private static void checkRoundTrip_simple(Encoder encoder, Decoder decoder) throws Exception {
783         assertRoundTrip(encoder, decoder, "YQ==", "a".getBytes(US_ASCII));
784         assertRoundTrip(encoder, decoder, "YWI=", "ab".getBytes(US_ASCII));
785         assertRoundTrip(encoder, decoder, "YWJj", "abc".getBytes(US_ASCII));
786         assertRoundTrip(encoder, decoder, "YWJjZA==", "abcd".getBytes(US_ASCII));
787     }
788 
789     /** check a range of possible line lengths */
testRoundTrip_allBytes_mime_lineLength()790     public void testRoundTrip_allBytes_mime_lineLength() {
791         Decoder decoder = Base64.getMimeDecoder();
792         byte[] separator = new byte[] { '*' };
793         checkRoundTrip_allBytes(Base64.getMimeEncoder(4, separator), decoder,
794                 wrapLines("*", ALL_BYTE_VALUES_ENCODED, 4));
795         checkRoundTrip_allBytes(Base64.getMimeEncoder(8, separator), decoder,
796                 wrapLines("*", ALL_BYTE_VALUES_ENCODED, 8));
797         checkRoundTrip_allBytes(Base64.getMimeEncoder(20, separator), decoder,
798                 wrapLines("*", ALL_BYTE_VALUES_ENCODED, 20));
799         checkRoundTrip_allBytes(Base64.getMimeEncoder(100, separator), decoder,
800                 wrapLines("*", ALL_BYTE_VALUES_ENCODED, 100));
801         checkRoundTrip_allBytes(Base64.getMimeEncoder(Integer.MAX_VALUE & ~3, separator), decoder,
802                 wrapLines("*", ALL_BYTE_VALUES_ENCODED, Integer.MAX_VALUE & ~3));
803     }
804 
testRoundTrip_allBytes_mime_lineLength_defaultsTo76Chars()805     public void testRoundTrip_allBytes_mime_lineLength_defaultsTo76Chars() {
806         checkRoundTrip_allBytes(Base64.getMimeEncoder(), Base64.getMimeDecoder(),
807                 wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 76));
808     }
809 
810     /**
811      * checks that the specified line length is rounded down to the nearest multiple of 4.
812      */
testRoundTrip_allBytes_mime_lineLength_isRoundedDown()813     public void testRoundTrip_allBytes_mime_lineLength_isRoundedDown() {
814         Decoder decoder = Base64.getMimeDecoder();
815         byte[] separator = new byte[] { '\r', '\n' };
816         checkRoundTrip_allBytes(Base64.getMimeEncoder(60, separator), decoder,
817                 wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 60));
818         checkRoundTrip_allBytes(Base64.getMimeEncoder(63, separator), decoder,
819                 wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 60));
820         checkRoundTrip_allBytes(Base64.getMimeEncoder(10, separator), decoder,
821                 wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 8));
822     }
823 
testRoundTrip_allBytes_url()824     public void testRoundTrip_allBytes_url() {
825         String encodedUrl = ALL_BYTE_VALUES_ENCODED.replace('+', '-').replace('/', '_');
826         checkRoundTrip_allBytes(Base64.getUrlEncoder(), Base64.getUrlDecoder(), encodedUrl);
827     }
828 
829     /**
830      * Checks round-trip encoding/decoding of all byte values 0..255 for
831      * the case where the Encoder doesn't add any linebreaks.
832      */
checkRoundTrip_allBytes_singleLine(Encoder encoder, Decoder decoder)833     private static void checkRoundTrip_allBytes_singleLine(Encoder encoder, Decoder decoder) {
834         checkRoundTrip_allBytes(encoder, decoder, ALL_BYTE_VALUES_ENCODED);
835     }
836 
837     /**
838      * Checks that byte values 0..255, in order, are encoded to exactly
839      * the given String (including any linebreaks, if present) and that
840      * that String can be decoded back to the same byte values.
841      *
842      * @param encoded the expected encoded representation of the (unsigned)
843      *        byte values 0..255, in order.
844      */
checkRoundTrip_allBytes(Encoder encoder, Decoder decoder, String encoded)845     private static void checkRoundTrip_allBytes(Encoder encoder, Decoder decoder, String encoded) {
846         byte[] bytes = new byte[256];
847         for (int i = 0; i < 256; i++) {
848             bytes[i] = (byte) i;
849         }
850         assertRoundTrip(encoder, decoder, encoded, bytes);
851     }
852 
testRoundTrip_variousSizes_plain()853     public void testRoundTrip_variousSizes_plain() {
854         checkRoundTrip_variousSizes(Base64.getEncoder(), Base64.getDecoder());
855     }
856 
testRoundTrip_variousSizes_mime()857     public void testRoundTrip_variousSizes_mime() {
858         checkRoundTrip_variousSizes(Base64.getMimeEncoder(), Base64.getMimeDecoder());
859     }
860 
testRoundTrip_variousSizes_url()861     public void testRoundTrip_variousSizes_url() {
862         checkRoundTrip_variousSizes(Base64.getUrlEncoder(), Base64.getUrlDecoder());
863     }
864 
865     /**
866      * Checks that various-sized inputs survive a round trip.
867      */
checkRoundTrip_variousSizes(Encoder encoder, Decoder decoder)868     private static void checkRoundTrip_variousSizes(Encoder encoder, Decoder decoder) {
869         Random random = new Random(7654321);
870         for (int numBytes : new int [] { 0, 1, 2, 75, 76, 77, 80, 100, 1234 }) {
871             byte[] bytes = new byte[numBytes];
872             random.nextBytes(bytes);
873             byte[] result = decoder.decode(encoder.encode(bytes));
874             assertArrayEquals(bytes, result);
875         }
876     }
877 
testRoundtrip_wrap_basic()878     public void testRoundtrip_wrap_basic() throws Exception {
879         Encoder encoder = Base64.getEncoder();
880         Decoder decoder = Base64.getDecoder();
881         checkRoundTrip_wrapInputStream(encoder, decoder);
882     }
883 
testRoundtrip_wrap_mime()884     public void testRoundtrip_wrap_mime() throws Exception {
885         Encoder encoder = Base64.getMimeEncoder();
886         Decoder decoder = Base64.getMimeDecoder();
887         checkRoundTrip_wrapInputStream(encoder, decoder);
888     }
889 
testRoundTrip_wrap_url()890     public void testRoundTrip_wrap_url() throws Exception {
891         Encoder encoder = Base64.getUrlEncoder();
892         Decoder decoder = Base64.getUrlDecoder();
893         checkRoundTrip_wrapInputStream(encoder, decoder);
894     }
895 
896     /**
897      * Checks that the {@link Decoder#wrap(InputStream) wrapping} an
898      * InputStream of encoded data yields the plain data that was
899      * previously {@link Encoder#encode(byte[]) encoded}.
900      */
checkRoundTrip_wrapInputStream(Encoder encoder, Decoder decoder)901     private static void checkRoundTrip_wrapInputStream(Encoder encoder, Decoder decoder)
902             throws IOException {
903         Random random = new Random(32176L);
904         int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
905 
906         // Test input needs to be at least 2048 bytes to fill up the
907         // read buffer of Base64InputStream.
908         byte[] plain = new byte[4567];
909         random.nextBytes(plain);
910         byte[] encoded = encoder.encode(plain);
911         byte[] actual = new byte[plain.length * 2];
912         int b;
913 
914         // ----- test decoding ("encoded" -> "plain") -----
915 
916         // read as much as it will give us in one chunk
917         ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
918         InputStream b64is = decoder.wrap(bais);
919         int ap = 0;
920         while ((b = b64is.read(actual, ap, actual.length - ap)) != -1) {
921             ap += b;
922         }
923         assertArrayPrefixEquals(actual, ap, plain);
924 
925         // read individual bytes
926         bais = new ByteArrayInputStream(encoded);
927         b64is = decoder.wrap(bais);
928         ap = 0;
929         while ((b = b64is.read()) != -1) {
930             actual[ap++] = (byte) b;
931         }
932         assertArrayPrefixEquals(actual, ap, plain);
933 
934         // mix reads of variously-sized arrays with one-byte reads
935         bais = new ByteArrayInputStream(encoded);
936         b64is = decoder.wrap(bais);
937         ap = 0;
938         while (true) {
939             int l = writeLengths[random.nextInt(writeLengths.length)];
940             if (l >= 0) {
941                 b = b64is.read(actual, ap, l);
942                 if (b == -1) {
943                     break;
944                 }
945                 ap += b;
946             } else {
947                 for (int i = 0; i < -l; ++i) {
948                     if ((b = b64is.read()) == -1) {
949                         break;
950                     }
951                     actual[ap++] = (byte) b;
952                 }
953             }
954         }
955         assertArrayPrefixEquals(actual, ap, plain);
956     }
957 
testDecoder_wrap_singleByteReads()958     public void testDecoder_wrap_singleByteReads() throws IOException {
959         InputStream in = Base64.getDecoder().wrap(new ByteArrayInputStream("/v8=".getBytes()));
960         assertEquals(254, in.read());
961         assertEquals(255, in.read());
962         assertEquals(-1, in.read());
963     }
964 
testEncoder_withoutPadding()965     public void testEncoder_withoutPadding() {
966         byte[] bytes = new byte[] { (byte) 0xFE, (byte) 0xFF };
967         assertEquals("/v8=", Base64.getEncoder().encodeToString(bytes));
968         assertEquals("/v8", Base64.getEncoder().withoutPadding().encodeToString(bytes));
969 
970         assertEquals("/v8=", Base64.getMimeEncoder().encodeToString(bytes));
971         assertEquals("/v8", Base64.getMimeEncoder().withoutPadding().encodeToString(bytes));
972 
973         assertEquals("_v8=", Base64.getUrlEncoder().encodeToString(bytes));
974         assertEquals("_v8", Base64.getUrlEncoder().withoutPadding().encodeToString(bytes));
975     }
976 
testEncoder_wrap_plain()977     public void testEncoder_wrap_plain() throws Exception {
978         checkWrapOutputStreamConsistentWithEncode(Base64.getEncoder());
979     }
980 
testEncoder_wrap_url()981     public void testEncoder_wrap_url() throws Exception {
982         checkWrapOutputStreamConsistentWithEncode(Base64.getUrlEncoder());
983     }
984 
testEncoder_wrap_mime()985     public void testEncoder_wrap_mime() throws Exception {
986         checkWrapOutputStreamConsistentWithEncode(Base64.getMimeEncoder());
987     }
988 
989     /** A way of writing bytes to an OutputStream. */
990     interface WriteStrategy {
write(byte[] bytes, OutputStream out)991         void write(byte[] bytes, OutputStream out) throws IOException;
992     }
993 
checkWrapOutputStreamConsistentWithEncode(Encoder encoder)994     private static void checkWrapOutputStreamConsistentWithEncode(Encoder encoder)
995             throws Exception {
996         final Random random = new Random(32176L);
997 
998         // one large write(byte[]) of the whole input
999         WriteStrategy allAtOnce = (bytes, out) -> out.write(bytes);
1000         checkWrapOutputStreamConsistentWithEncode(encoder, allAtOnce);
1001 
1002         // many calls to write(int)
1003         WriteStrategy byteWise = (bytes, out) -> {
1004             for (byte b : bytes) {
1005                 out.write(b);
1006             }
1007         };
1008         checkWrapOutputStreamConsistentWithEncode(encoder, byteWise);
1009 
1010         // intermixed sequences of write(int) with
1011         // write(byte[],int,int) of various lengths.
1012         WriteStrategy mixed = (bytes, out) -> {
1013             int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
1014             int p = 0;
1015             while (p < bytes.length) {
1016                 int l = writeLengths[random.nextInt(writeLengths.length)];
1017                 l = Math.min(l, bytes.length - p);
1018                 if (l >= 0) {
1019                     out.write(bytes, p, l);
1020                     p += l;
1021                 } else {
1022                     l = Math.min(-l, bytes.length - p);
1023                     for (int i = 0; i < l; ++i) {
1024                         out.write(bytes[p + i]);
1025                     }
1026                     p += l;
1027                 }
1028             }
1029         };
1030         checkWrapOutputStreamConsistentWithEncode(encoder, mixed);
1031     }
1032 
1033     /**
1034      * Checks that writing to a wrap()ping OutputStream produces the same
1035      * output on the wrapped stream as {@link Encoder#encode(byte[])}.
1036      */
checkWrapOutputStreamConsistentWithEncode(Encoder encoder, WriteStrategy writeStrategy)1037     private static void checkWrapOutputStreamConsistentWithEncode(Encoder encoder,
1038             WriteStrategy writeStrategy) throws IOException {
1039         Random random = new Random(32176L);
1040         // Test input needs to be at least 1024 bytes to test filling
1041         // up the write(int) buffer of Base64OutputStream.
1042         byte[] plain = new byte[1234];
1043         random.nextBytes(plain);
1044         byte[] encodeResult = encoder.encode(plain);
1045         ByteArrayOutputStream wrappedOutputStream = new ByteArrayOutputStream();
1046         try (OutputStream plainOutputStream = encoder.wrap(wrappedOutputStream)) {
1047             writeStrategy.write(plain, plainOutputStream);
1048         }
1049         assertArrayEquals(encodeResult, wrappedOutputStream.toByteArray());
1050     }
1051 
1052     /** Decodes a string, returning the resulting bytes interpreted as an ASCII String. */
decodeToAscii(Decoder decoder, String encoded)1053     private static String decodeToAscii(Decoder decoder, String encoded) throws Exception {
1054         byte[] plain = decoder.decode(encoded);
1055         return new String(plain, US_ASCII);
1056     }
1057 
1058     /**
1059      * Checks round-trip encoding/decoding of {@code plain}.
1060      *
1061      * @param plain an ASCII String
1062      * @return the Base64-encoded value of the ASCII codepoints from {@code plain}
1063      */
encodeFromAscii(Encoder encoder, Decoder decoder, String plain)1064     private static String encodeFromAscii(Encoder encoder, Decoder decoder, String plain)
1065             throws Exception {
1066         String encoded = encoder.encodeToString(plain.getBytes(US_ASCII));
1067         String decoded = decodeToAscii(decoder, encoded);
1068         assertEquals(plain, decoded);
1069         return encoded;
1070     }
1071 
1072     /**
1073      * Rewraps {@code s} by inserting {@lineSeparator} every {@code lineLength} characters,
1074      * but not at the end.
1075      */
wrapLines(String lineSeparator, String s, int lineLength)1076     private static String wrapLines(String lineSeparator, String s, int lineLength) {
1077         return String.join(lineSeparator, breakLines(s, lineLength));
1078     }
1079 
1080     /**
1081      * Splits {@code s} into a list of substrings, each except possibly the last one
1082      * exactly {@code lineLength} characters long.
1083      */
breakLines(String longString, int lineLength)1084     private static List<String> breakLines(String longString, int lineLength) {
1085         List<String> lines = new ArrayList<>();
1086         for (int pos = 0; pos < longString.length(); pos += lineLength) {
1087             lines.add(longString.substring(pos, Math.min(longString.length(), pos + lineLength)));
1088         }
1089         return lines;
1090     }
1091 
1092     /** Assert that decoding the specific String throws IllegalArgumentException. */
assertDecodeThrowsIAe(Decoder decoder, String invalidEncoded)1093     private static void assertDecodeThrowsIAe(Decoder decoder, String invalidEncoded)
1094             throws Exception {
1095         try {
1096             decoder.decode(invalidEncoded);
1097             fail("should have failed to decode");
1098         } catch (IllegalArgumentException e) {
1099         }
1100     }
1101 
1102     /**
1103      * Asserts that the given String decodes to the bytes, and that the bytes encode
1104      * to the given String.
1105      */
assertRoundTrip(Encoder encoder, Decoder decoder, String encoded, byte[] bytes)1106     private static void assertRoundTrip(Encoder encoder, Decoder decoder, String encoded,
1107             byte[] bytes) {
1108         assertEquals(encoded, encoder.encodeToString(bytes));
1109         assertArrayEquals(bytes, decoder.decode(encoded));
1110     }
1111 
1112     /** Asserts that actual equals the first len bytes of expected. */
assertArrayPrefixEquals(byte[] expected, int len, byte[] actual)1113     private static void assertArrayPrefixEquals(byte[] expected, int len, byte[] actual) {
1114         assertArrayEquals(copyOfRange(expected, 0, len), actual);
1115     }
1116 
1117     /** Checks array contents. */
assertArrayEquals(byte[] expected, byte[] actual)1118     private static void assertArrayEquals(byte[] expected, byte[] actual) {
1119         if (!Arrays.equals(expected, actual)) {
1120             fail("Expected " + HexEncoding.encodeToString(expected)
1121                     + ", got " + HexEncoding.encodeToString(actual));
1122         }
1123     }
1124 
assertThrowsNpe(Runnable runnable)1125     private static void assertThrowsNpe(Runnable runnable) {
1126         try {
1127             runnable.run();
1128             fail("Should have thrown NullPointerException");
1129         } catch (NullPointerException expected) {
1130         }
1131     }
1132 
1133 }
1134