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