1 /* 2 * Copyright (C) 2010 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 android.util.cts; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.fail; 22 23 import android.util.Base64; 24 import android.util.Base64InputStream; 25 import android.util.Base64OutputStream; 26 27 import androidx.test.filters.LargeTest; 28 import androidx.test.runner.AndroidJUnit4; 29 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 33 import java.io.ByteArrayInputStream; 34 import java.io.ByteArrayOutputStream; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.OutputStream; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Random; 42 import java.util.stream.Collectors; 43 44 @LargeTest 45 @RunWith(AndroidJUnit4.class) 46 public class Base64Test { 47 private static final String TAG = "Base64Test"; 48 49 /** Decodes a string, returning a string. */ decodeString(String in)50 private String decodeString(String in) throws Exception { 51 byte[] out = Base64.decode(in, 0); 52 return new String(out); 53 } 54 55 /** 56 * Encodes the string 'in' using 'flags'. Asserts that decoding 57 * gives the same string. Returns the encoded string. 58 */ encodeToString(String in, int flags)59 private String encodeToString(String in, int flags) throws Exception { 60 String b64 = Base64.encodeToString(in.getBytes(), flags); 61 String dec = decodeString(b64); 62 assertEquals(in, dec); 63 return b64; 64 } 65 66 /** Assert that decoding 'in' throws IllegalArgumentException. */ assertBad(String in)67 private void assertBad(String in) throws Exception { 68 try { 69 byte[] out = Base64.decode(in, 0); 70 fail("should have failed to decode"); 71 } catch (IllegalArgumentException e) { 72 } 73 } 74 75 /** Assert that actual equals the first len bytes of expected. */ assertPartialEquals(byte[] expected, int len, byte[] actual)76 private void assertPartialEquals(byte[] expected, int len, byte[] actual) { 77 assertEquals(len, actual.length); 78 for (int i = 0; i < len; ++i) { 79 assertEquals(expected[i], actual[i]); 80 } 81 } 82 83 /** Assert that actual equals the first len bytes of expected. */ assertPartialEquals(byte[] expected, int len, byte[] actual, int alen)84 private void assertPartialEquals(byte[] expected, int len, byte[] actual, int alen) { 85 assertEquals(len, alen); 86 for (int i = 0; i < len; ++i) { 87 assertEquals(expected[i], actual[i]); 88 } 89 } 90 91 @Test testDecodeExtraChars()92 public void testDecodeExtraChars() throws Exception { 93 // padding 0 94 assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk")); 95 assertBad("aGVsbG8sIHdvcmxk="); 96 assertBad("aGVsbG8sIHdvcmxk=="); 97 assertBad("aGVsbG8sIHdvcmxk ="); 98 assertBad("aGVsbG8sIHdvcmxk = = "); 99 assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk ")); 100 assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k ")); 101 assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk ")); 102 assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk ")); 103 assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k ")); 104 assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_")); 105 assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk")); 106 107 // padding 1 108 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE=")); 109 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE")); 110 assertBad("aGVsbG8sIHdvcmxkPyE=="); 111 assertBad("aGVsbG8sIHdvcmxkPyE =="); 112 assertBad("aGVsbG8sIHdvcmxkPyE = = "); 113 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E=")); 114 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E")); 115 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E =")); 116 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ")); 117 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = ")); 118 assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ")); 119 120 // padding 2 121 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg==")); 122 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg")); 123 assertBad("aGVsbG8sIHdvcmxkLg="); 124 assertBad("aGVsbG8sIHdvcmxkLg ="); 125 assertBad("aGVsbG8sIHdvcmxkLg = "); 126 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g==")); 127 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g")); 128 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g ==")); 129 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g ")); 130 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = ")); 131 assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g ")); 132 } 133 134 private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd, 135 (byte) 0xcc, (byte) 0xbb, (byte) 0xaa, 136 (byte) 0x99, (byte) 0x88, (byte) 0x77 }; 137 138 @Test testBinaryDecode()139 public void testBinaryDecode() throws Exception { 140 assertPartialEquals(BYTES, 0, Base64.decode("", 0)); 141 assertPartialEquals(BYTES, 1, Base64.decode("/w==", 0)); 142 assertPartialEquals(BYTES, 2, Base64.decode("/+4=", 0)); 143 assertPartialEquals(BYTES, 3, Base64.decode("/+7d", 0)); 144 assertPartialEquals(BYTES, 4, Base64.decode("/+7dzA==", 0)); 145 assertPartialEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0)); 146 assertPartialEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0)); 147 assertPartialEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0)); 148 assertPartialEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0)); 149 } 150 151 @Test testWebSafe()152 public void testWebSafe() throws Exception { 153 assertPartialEquals(BYTES, 0, Base64.decode("", Base64.URL_SAFE)); 154 assertPartialEquals(BYTES, 1, Base64.decode("_w==", Base64.URL_SAFE)); 155 assertPartialEquals(BYTES, 2, Base64.decode("_-4=", Base64.URL_SAFE)); 156 assertPartialEquals(BYTES, 3, Base64.decode("_-7d", Base64.URL_SAFE)); 157 assertPartialEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.URL_SAFE)); 158 assertPartialEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.URL_SAFE)); 159 assertPartialEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.URL_SAFE)); 160 assertPartialEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.URL_SAFE)); 161 assertPartialEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.URL_SAFE)); 162 163 assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.URL_SAFE)); 164 assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.URL_SAFE)); 165 assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.URL_SAFE)); 166 assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.URL_SAFE)); 167 assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.URL_SAFE)); 168 assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.URL_SAFE)); 169 assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.URL_SAFE)); 170 assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.URL_SAFE)); 171 assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.URL_SAFE)); 172 } 173 174 @Test testFlags()175 public void testFlags() throws Exception { 176 assertEquals("YQ==\n", encodeToString("a", 0)); 177 assertEquals("YQ==", encodeToString("a", Base64.NO_WRAP)); 178 assertEquals("YQ\n", encodeToString("a", Base64.NO_PADDING)); 179 assertEquals("YQ", encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP)); 180 assertEquals("YQ==\r\n", encodeToString("a", Base64.CRLF)); 181 assertEquals("YQ\r\n", encodeToString("a", Base64.CRLF | Base64.NO_PADDING)); 182 183 assertEquals("YWI=\n", encodeToString("ab", 0)); 184 assertEquals("YWI=", encodeToString("ab", Base64.NO_WRAP)); 185 assertEquals("YWI\n", encodeToString("ab", Base64.NO_PADDING)); 186 assertEquals("YWI", encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP)); 187 assertEquals("YWI=\r\n", encodeToString("ab", Base64.CRLF)); 188 assertEquals("YWI\r\n", encodeToString("ab", Base64.CRLF | Base64.NO_PADDING)); 189 190 assertEquals("YWJj\n", encodeToString("abc", 0)); 191 assertEquals("YWJj", encodeToString("abc", Base64.NO_WRAP)); 192 assertEquals("YWJj\n", encodeToString("abc", Base64.NO_PADDING)); 193 assertEquals("YWJj", encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP)); 194 assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF)); 195 assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF | Base64.NO_PADDING)); 196 197 assertEquals("YWJjZA==\n", encodeToString("abcd", 0)); 198 assertEquals("YWJjZA==", encodeToString("abcd", Base64.NO_WRAP)); 199 assertEquals("YWJjZA\n", encodeToString("abcd", Base64.NO_PADDING)); 200 assertEquals("YWJjZA", encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP)); 201 assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF)); 202 assertEquals("YWJjZA\r\n", encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING)); 203 } 204 205 @Test testLineLength()206 public void testLineLength() throws Exception { 207 String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd"; 208 String in_57 = in_56 + "e"; 209 String in_58 = in_56 + "ef"; 210 String in_59 = in_56 + "efg"; 211 String in_60 = in_56 + "efgh"; 212 String in_61 = in_56 + "efghi"; 213 214 String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi"; 215 String out_56 = prefix + "Y2Q=\n"; 216 String out_57 = prefix + "Y2Rl\n"; 217 String out_58 = prefix + "Y2Rl\nZg==\n"; 218 String out_59 = prefix + "Y2Rl\nZmc=\n"; 219 String out_60 = prefix + "Y2Rl\nZmdo\n"; 220 String out_61 = prefix + "Y2Rl\nZmdoaQ==\n"; 221 222 // no newline for an empty input array. 223 assertEquals("", encodeToString("", 0)); 224 225 assertEquals(out_56, encodeToString(in_56, 0)); 226 assertEquals(out_57, encodeToString(in_57, 0)); 227 assertEquals(out_58, encodeToString(in_58, 0)); 228 assertEquals(out_59, encodeToString(in_59, 0)); 229 assertEquals(out_60, encodeToString(in_60, 0)); 230 assertEquals(out_61, encodeToString(in_61, 0)); 231 232 assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING)); 233 assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING)); 234 assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING)); 235 assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING)); 236 assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING)); 237 assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING)); 238 239 assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP)); 240 assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP)); 241 assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP)); 242 assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP)); 243 assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP)); 244 assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP)); 245 } 246 247 private static final String lipsum = 248 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + 249 "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " + 250 "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " + 251 "urna, pharetra vitae consequat eget, adipiscing eu ante. " + 252 "Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " + 253 "eget lorem aliquam rutrum vel vitae eros. In placerat ornare " + 254 "pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " + 255 "eu ultrices suscipit, ligula nisi consectetur eros, dapibus " + 256 "aliquet dui sapien a turpis. Donec ultricies varius ligula, " + 257 "ut hendrerit arcu malesuada at. Praesent sed elit pretium " + 258 "eros luctus gravida. In ac dolor lorem. Cras condimentum " + 259 "convallis elementum. Phasellus vel felis in nulla ultrices " + 260 "venenatis. Nam non tortor non orci convallis convallis. " + 261 "Nam tristique lacinia hendrerit. Pellentesque habitant morbi " + 262 "tristique senectus et netus et malesuada fames ac turpis " + 263 "egestas. Vivamus cursus, nibh eu imperdiet porta, magna " + 264 "ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " + 265 "Phasellus posuere, leo at ultricies vehicula, massa risus " + 266 "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " + 267 "molestie dapibus commodo. Ut vel tellus at massa gravida " + 268 "semper non sed orci."; 269 270 @Test testInputStream()271 public void testInputStream() throws Exception { 272 int[] flagses = { Base64.DEFAULT, 273 Base64.NO_PADDING, 274 Base64.NO_WRAP, 275 Base64.NO_PADDING | Base64.NO_WRAP, 276 Base64.CRLF, 277 Base64.URL_SAFE }; 278 int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 }; 279 Random rng = new Random(32176L); 280 281 // Test input needs to be at least 2048 bytes to fill up the 282 // read buffer of Base64InputStream. 283 byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes(); 284 285 for (int flags: flagses) { 286 byte[] encoded = Base64.encode(plain, flags); 287 288 ByteArrayInputStream bais; 289 Base64InputStream b64is; 290 byte[] actual = new byte[plain.length * 2]; 291 int ap; 292 int b; 293 294 // ----- test decoding ("encoded" -> "plain") ----- 295 296 // read as much as it will give us in one chunk 297 bais = new ByteArrayInputStream(encoded); 298 b64is = new Base64InputStream(bais, flags); 299 ap = 0; 300 while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) { 301 ap += b; 302 } 303 assertPartialEquals(actual, ap, plain); 304 305 // read individual bytes 306 bais = new ByteArrayInputStream(encoded); 307 b64is = new Base64InputStream(bais, flags); 308 ap = 0; 309 while ((b = b64is.read()) != -1) { 310 actual[ap++] = (byte) b; 311 } 312 assertPartialEquals(actual, ap, plain); 313 314 // mix reads of variously-sized arrays with one-byte reads 315 bais = new ByteArrayInputStream(encoded); 316 b64is = new Base64InputStream(bais, flags); 317 ap = 0; 318 readloop: while (true) { 319 int l = writeLengths[rng.nextInt(writeLengths.length)]; 320 if (l >= 0) { 321 b = b64is.read(actual, ap, l); 322 if (b == -1) break readloop; 323 ap += b; 324 } else { 325 for (int i = 0; i < -l; ++i) { 326 if ((b = b64is.read()) == -1) break readloop; 327 actual[ap++] = (byte) b; 328 } 329 } 330 } 331 assertPartialEquals(actual, ap, plain); 332 } 333 } 334 335 /** http://b/3026478 */ 336 @Test testSingleByteReads()337 public void testSingleByteReads() throws IOException { 338 InputStream in = new Base64InputStream( 339 new ByteArrayInputStream("/v8=".getBytes()), Base64.DEFAULT); 340 assertEquals(254, in.read()); 341 assertEquals(255, in.read()); 342 } 343 344 /** 345 * Tests that Base64OutputStream produces exactly the same results 346 * as calling Base64.encode/.decode on an in-memory array. 347 */ 348 @Test testOutputStream()349 public void testOutputStream() throws Exception { 350 int[] flagses = { Base64.DEFAULT, 351 Base64.NO_PADDING, 352 Base64.NO_WRAP, 353 Base64.NO_PADDING | Base64.NO_WRAP, 354 Base64.CRLF, 355 Base64.URL_SAFE }; 356 int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 }; 357 Random rng = new Random(32176L); 358 359 // Test input needs to be at least 1024 bytes to test filling 360 // up the write(int) buffer of Base64OutputStream. 361 byte[] plain = (lipsum + lipsum).getBytes(); 362 363 for (int flags: flagses) { 364 byte[] encoded = Base64.encode(plain, flags); 365 366 ByteArrayOutputStream baos; 367 Base64OutputStream b64os; 368 byte[] actual; 369 int p; 370 371 // ----- test encoding ("plain" -> "encoded") ----- 372 373 // one large write(byte[]) of the whole input 374 baos = new ByteArrayOutputStream(); 375 b64os = new Base64OutputStream(baos, flags); 376 b64os.write(plain); 377 b64os.close(); 378 actual = baos.toByteArray(); 379 assertArrayEquals(encoded, actual); 380 381 // many calls to write(int) 382 baos = new ByteArrayOutputStream(); 383 b64os = new Base64OutputStream(baos, flags); 384 for (int i = 0; i < plain.length; ++i) { 385 b64os.write(plain[i]); 386 } 387 b64os.close(); 388 actual = baos.toByteArray(); 389 assertArrayEquals(encoded, actual); 390 391 // intermixed sequences of write(int) with 392 // write(byte[],int,int) of various lengths. 393 baos = new ByteArrayOutputStream(); 394 b64os = new Base64OutputStream(baos, flags); 395 p = 0; 396 while (p < plain.length) { 397 int l = writeLengths[rng.nextInt(writeLengths.length)]; 398 l = Math.min(l, plain.length-p); 399 if (l >= 0) { 400 b64os.write(plain, p, l); 401 p += l; 402 } else { 403 l = Math.min(-l, plain.length-p); 404 for (int i = 0; i < l; ++i) { 405 b64os.write(plain[p+i]); 406 } 407 p += l; 408 } 409 } 410 b64os.close(); 411 actual = baos.toByteArray(); 412 assertArrayEquals(encoded, actual); 413 } 414 } 415 416 @Test testOutputStream_ioExceptionDuringClose()417 public void testOutputStream_ioExceptionDuringClose() { 418 OutputStream out = new OutputStream() { 419 @Override public void write(int b) throws IOException { } 420 @Override public void close() throws IOException { 421 throw new IOException("close()"); 422 } 423 }; 424 OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT); 425 try { 426 out2.close(); 427 fail(); 428 } catch (IOException expected) { 429 } 430 } 431 432 @Test testOutputStream_ioExceptionDuringCloseAndWrite()433 public void testOutputStream_ioExceptionDuringCloseAndWrite() { 434 OutputStream out = new OutputStream() { 435 @Override public void write(int b) throws IOException { 436 throw new IOException("write()"); 437 } 438 @Override public void write(byte[] b) throws IOException { 439 throw new IOException("write()"); 440 } 441 @Override public void write(byte[] b, int off, int len) throws IOException { 442 throw new IOException("write()"); 443 } 444 @Override public void close() throws IOException { 445 throw new IOException("close()"); 446 } 447 }; 448 OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT); 449 try { 450 out2.close(); 451 fail(); 452 } catch (IOException expected) { 453 // Base64OutputStream write()s pending (possibly empty) data 454 // before close(), so the IOE from write() should be thrown and 455 // any later exception suppressed. 456 assertEquals("write()", expected.getMessage()); 457 Throwable[] suppressed = expected.getSuppressed(); 458 List<String> suppressedMessages = Arrays.asList(suppressed).stream() 459 .map((e) -> e.getMessage()) 460 .collect(Collectors.toList()); 461 assertEquals(Collections.singletonList("close()"), suppressedMessages); 462 } 463 } 464 465 @Test testOutputStream_ioExceptionDuringWrite()466 public void testOutputStream_ioExceptionDuringWrite() { 467 OutputStream out = new OutputStream() { 468 @Override public void write(int b) throws IOException { 469 throw new IOException("write()"); 470 } 471 @Override public void write(byte[] b) throws IOException { 472 throw new IOException("write()"); 473 } 474 @Override public void write(byte[] b, int off, int len) throws IOException { 475 throw new IOException("write()"); 476 } 477 }; 478 // Base64OutputStream write()s pending (possibly empty) data 479 // before close(), so the IOE from write() should be thrown. 480 OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT); 481 try { 482 out2.close(); 483 fail(); 484 } catch (IOException expected) { 485 } 486 } 487 488 } 489