1 /* 2 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 4235519 8004212 8005394 8007298 8006295 8006315 8006530 8007379 8008925 26 * 8014217 8025003 8026330 8028397 8165243 27 * @summary tests java.util.Base64 28 * @library /lib/testlibrary 29 * @build jdk.testlibrary.* 30 * @run main TestBase64 31 */ 32 33 import java.io.ByteArrayInputStream; 34 import java.io.ByteArrayOutputStream; 35 import java.io.InputStream; 36 import java.io.IOException; 37 import java.io.OutputStream; 38 import java.nio.ByteBuffer; 39 import java.util.Arrays; 40 import java.util.ArrayList; 41 import java.util.Base64; 42 import java.util.List; 43 import java.util.Random; 44 45 import jdk.testlibrary.RandomFactory; 46 47 public class TestBase64 { 48 49 private static final Random rnd = RandomFactory.getRandom(); 50 51 public static void main(String args[]) throws Throwable { 52 int numRuns = 10; 53 int numBytes = 200; 54 if (args.length > 1) { 55 numRuns = Integer.parseInt(args[0]); 56 numBytes = Integer.parseInt(args[1]); 57 } 58 59 test(Base64.getEncoder(), Base64.getDecoder(), numRuns, numBytes); 60 test(Base64.getUrlEncoder(), Base64.getUrlDecoder(), numRuns, numBytes); 61 test(Base64.getMimeEncoder(), Base64.getMimeDecoder(), numRuns, numBytes); 62 63 byte[] nl_1 = new byte[] {'\n'}; 64 byte[] nl_2 = new byte[] {'\n', '\r'}; 65 byte[] nl_3 = new byte[] {'\n', '\r', '\n'}; 66 for (int i = 0; i < 10; i++) { 67 int len = rnd.nextInt(200) + 4; 68 test(Base64.getMimeEncoder(len, nl_1), 69 Base64.getMimeDecoder(), 70 numRuns, numBytes); 71 test(Base64.getMimeEncoder(len, nl_2), 72 Base64.getMimeDecoder(), 73 numRuns, numBytes); 74 test(Base64.getMimeEncoder(len, nl_3), 75 Base64.getMimeDecoder(), 76 numRuns, numBytes); 77 } 78 79 testNull(Base64.getEncoder()); 80 testNull(Base64.getUrlEncoder()); 81 testNull(Base64.getMimeEncoder()); 82 testNull(Base64.getMimeEncoder(10, new byte[]{'\n'})); 83 testNull(Base64.getDecoder()); 84 testNull(Base64.getUrlDecoder()); 85 testNull(Base64.getMimeDecoder()); 86 checkNull(() -> Base64.getMimeEncoder(10, null)); 87 88 testIOE(Base64.getEncoder()); 89 testIOE(Base64.getUrlEncoder()); 90 testIOE(Base64.getMimeEncoder()); 91 testIOE(Base64.getMimeEncoder(10, new byte[]{'\n'})); 92 93 byte[] src = new byte[1024]; 94 rnd.nextBytes(src); 95 final byte[] decoded = Base64.getEncoder().encode(src); 96 testIOE(Base64.getDecoder(), decoded); 97 testIOE(Base64.getMimeDecoder(), decoded); 98 testIOE(Base64.getUrlDecoder(), Base64.getUrlEncoder().encode(src)); 99 100 // illegal line separator 101 checkIAE(() -> Base64.getMimeEncoder(10, new byte[]{'\r', 'N'})); 102 103 // malformed padding/ending 104 testMalformedPadding(); 105 106 // illegal base64 character 107 decoded[2] = (byte)0xe0; 108 checkIAE(() -> Base64.getDecoder().decode(decoded)); 109 checkIAE(() -> Base64.getDecoder().decode(decoded, new byte[1024])); 110 checkIAE(() -> Base64.getDecoder().decode(ByteBuffer.wrap(decoded))); 111 112 // test single-non-base64 character for mime decoding 113 testSingleNonBase64MimeDec(); 114 115 // test decoding of unpadded data 116 testDecodeUnpadded(); 117 118 // test mime decoding with ignored character after padding 119 testDecodeIgnoredAfterPadding(); 120 121 // given invalid args, encoder should not produce output 122 testEncoderKeepsSilence(Base64.getEncoder()); 123 testEncoderKeepsSilence(Base64.getUrlEncoder()); 124 testEncoderKeepsSilence(Base64.getMimeEncoder()); 125 126 // given invalid args, decoder should not consume input 127 testDecoderKeepsAbstinence(Base64.getDecoder()); 128 testDecoderKeepsAbstinence(Base64.getUrlDecoder()); 129 testDecoderKeepsAbstinence(Base64.getMimeDecoder()); 130 } 131 132 private static sun.misc.BASE64Encoder sunmisc = new sun.misc.BASE64Encoder(); 133 134 private static void test(Base64.Encoder enc, Base64.Decoder dec, 135 int numRuns, int numBytes) throws Throwable { 136 Random rnd = new java.util.Random(); 137 138 enc.encode(new byte[0]); 139 dec.decode(new byte[0]); 140 141 for (boolean withoutPadding : new boolean[] { false, true}) { 142 if (withoutPadding) { 143 enc = enc.withoutPadding(); 144 } 145 for (int i=0; i<numRuns; i++) { 146 for (int j=1; j<numBytes; j++) { 147 byte[] orig = new byte[j]; 148 rnd.nextBytes(orig); 149 150 // --------testing encode/decode(byte[])-------- 151 byte[] encoded = enc.encode(orig); 152 byte[] decoded = dec.decode(encoded); 153 154 checkEqual(orig, decoded, 155 "Base64 array encoding/decoding failed!"); 156 if (withoutPadding) { 157 if (encoded[encoded.length - 1] == '=') 158 throw new RuntimeException( 159 "Base64 enc.encode().withoutPadding() has padding!"); 160 } 161 // compare to sun.misc.BASE64Encoder 162 163 byte[] encoded2 = sunmisc.encode(orig).getBytes("ASCII"); 164 if (!withoutPadding) { // don't test for withoutPadding() 165 checkEqual(normalize(encoded), normalize(encoded2), 166 "Base64 enc.encode() does not match sun.misc.base64!"); 167 } 168 // remove padding '=' to test non-padding decoding case 169 if (encoded[encoded.length -2] == '=') 170 encoded2 = Arrays.copyOf(encoded, encoded.length -2); 171 else if (encoded[encoded.length -1] == '=') 172 encoded2 = Arrays.copyOf(encoded, encoded.length -1); 173 else 174 encoded2 = null; 175 176 // --------testing encodetoString(byte[])/decode(String)-------- 177 String str = enc.encodeToString(orig); 178 if (!Arrays.equals(str.getBytes("ASCII"), encoded)) { 179 throw new RuntimeException( 180 "Base64 encodingToString() failed!"); 181 } 182 byte[] buf = dec.decode(new String(encoded, "ASCII")); 183 checkEqual(buf, orig, "Base64 decoding(String) failed!"); 184 185 if (encoded2 != null) { 186 buf = dec.decode(new String(encoded2, "ASCII")); 187 checkEqual(buf, orig, "Base64 decoding(String) failed!"); 188 } 189 190 //-------- testing encode/decode(Buffer)-------- 191 testEncode(enc, ByteBuffer.wrap(orig), encoded); 192 ByteBuffer bin = ByteBuffer.allocateDirect(orig.length); 193 bin.put(orig).flip(); 194 testEncode(enc, bin, encoded); 195 196 testDecode(dec, ByteBuffer.wrap(encoded), orig); 197 bin = ByteBuffer.allocateDirect(encoded.length); 198 bin.put(encoded).flip(); 199 testDecode(dec, bin, orig); 200 201 if (encoded2 != null) 202 testDecode(dec, ByteBuffer.wrap(encoded2), orig); 203 204 // --------testing decode.wrap(input stream)-------- 205 // 1) random buf length 206 ByteArrayInputStream bais = new ByteArrayInputStream(encoded); 207 InputStream is = dec.wrap(bais); 208 buf = new byte[orig.length + 10]; 209 int len = orig.length; 210 int off = 0; 211 while (true) { 212 int n = rnd.nextInt(len); 213 if (n == 0) 214 n = 1; 215 n = is.read(buf, off, n); 216 if (n == -1) { 217 checkEqual(off, orig.length, 218 "Base64 stream decoding failed"); 219 break; 220 } 221 off += n; 222 len -= n; 223 if (len == 0) 224 break; 225 } 226 buf = Arrays.copyOf(buf, off); 227 checkEqual(buf, orig, "Base64 stream decoding failed!"); 228 229 // 2) read one byte each 230 bais.reset(); 231 is = dec.wrap(bais); 232 buf = new byte[orig.length + 10]; 233 off = 0; 234 int b; 235 while ((b = is.read()) != -1) { 236 buf[off++] = (byte)b; 237 } 238 buf = Arrays.copyOf(buf, off); 239 checkEqual(buf, orig, "Base64 stream decoding failed!"); 240 241 // --------testing encode.wrap(output stream)-------- 242 ByteArrayOutputStream baos = new ByteArrayOutputStream((orig.length + 2) / 3 * 4 + 10); 243 OutputStream os = enc.wrap(baos); 244 off = 0; 245 len = orig.length; 246 for (int k = 0; k < 5; k++) { 247 if (len == 0) 248 break; 249 int n = rnd.nextInt(len); 250 if (n == 0) 251 n = 1; 252 os.write(orig, off, n); 253 off += n; 254 len -= n; 255 } 256 if (len != 0) 257 os.write(orig, off, len); 258 os.close(); 259 buf = baos.toByteArray(); 260 checkEqual(buf, encoded, "Base64 stream encoding failed!"); 261 262 // 2) write one byte each 263 baos.reset(); 264 os = enc.wrap(baos); 265 off = 0; 266 while (off < orig.length) { 267 os.write(orig[off++]); 268 } 269 os.close(); 270 buf = baos.toByteArray(); 271 checkEqual(buf, encoded, "Base64 stream encoding failed!"); 272 273 // --------testing encode(in, out); -> bigger buf-------- 274 buf = new byte[encoded.length + rnd.nextInt(100)]; 275 int ret = enc.encode(orig, buf); 276 checkEqual(ret, encoded.length, 277 "Base64 enc.encode(src, null) returns wrong size!"); 278 buf = Arrays.copyOf(buf, ret); 279 checkEqual(buf, encoded, 280 "Base64 enc.encode(src, dst) failed!"); 281 282 // --------testing decode(in, out); -> bigger buf-------- 283 buf = new byte[orig.length + rnd.nextInt(100)]; 284 ret = dec.decode(encoded, buf); 285 checkEqual(ret, orig.length, 286 "Base64 enc.encode(src, null) returns wrong size!"); 287 buf = Arrays.copyOf(buf, ret); 288 checkEqual(buf, orig, 289 "Base64 dec.decode(src, dst) failed!"); 290 291 } 292 } 293 } 294 } 295 296 private static final byte[] ba_null = null; 297 private static final String str_null = null; 298 private static final ByteBuffer bb_null = null; 299 300 private static void testNull(Base64.Encoder enc) { 301 checkNull(() -> enc.encode(ba_null)); 302 checkNull(() -> enc.encodeToString(ba_null)); 303 checkNull(() -> enc.encode(ba_null, new byte[10])); 304 checkNull(() -> enc.encode(new byte[10], ba_null)); 305 checkNull(() -> enc.encode(bb_null)); 306 checkNull(() -> enc.wrap((OutputStream)null)); 307 } 308 309 private static void testNull(Base64.Decoder dec) { 310 checkNull(() -> dec.decode(ba_null)); 311 checkNull(() -> dec.decode(str_null)); 312 checkNull(() -> dec.decode(ba_null, new byte[10])); 313 checkNull(() -> dec.decode(new byte[10], ba_null)); 314 checkNull(() -> dec.decode(bb_null)); 315 checkNull(() -> dec.wrap((InputStream)null)); 316 } 317 318 @FunctionalInterface 319 private static interface Testable { 320 public void test() throws Throwable; 321 } 322 323 private static void testIOE(Base64.Encoder enc) throws Throwable { 324 ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); 325 OutputStream os = enc.wrap(baos); 326 os.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9}); 327 os.close(); 328 checkIOE(() -> os.write(10)); 329 checkIOE(() -> os.write(new byte[] {10})); 330 checkIOE(() -> os.write(new byte[] {10}, 1, 4)); 331 } 332 333 private static void testIOE(Base64.Decoder dec, byte[] decoded) throws Throwable { 334 ByteArrayInputStream bais = new ByteArrayInputStream(decoded); 335 InputStream is = dec.wrap(bais); 336 is.read(new byte[10]); 337 is.close(); 338 checkIOE(() -> is.read()); 339 checkIOE(() -> is.read(new byte[] {10})); 340 checkIOE(() -> is.read(new byte[] {10}, 1, 4)); 341 checkIOE(() -> is.available()); 342 checkIOE(() -> is.skip(20)); 343 } 344 345 private static final void checkNull(Runnable r) { 346 try { 347 r.run(); 348 throw new RuntimeException("NPE is not thrown as expected"); 349 } catch (NullPointerException npe) {} 350 } 351 352 private static final void checkIOE(Testable t) throws Throwable { 353 try { 354 t.test(); 355 throw new RuntimeException("IOE is not thrown as expected"); 356 } catch (IOException ioe) {} 357 } 358 359 private static final void checkIAE(Runnable r) throws Throwable { 360 try { 361 r.run(); 362 throw new RuntimeException("IAE is not thrown as expected"); 363 } catch (IllegalArgumentException iae) {} 364 } 365 366 private static void testDecodeIgnoredAfterPadding() throws Throwable { 367 for (byte nonBase64 : new byte[] {'#', '(', '!', '\\', '-', '_', '\n', '\r'}) { 368 byte[][] src = new byte[][] { 369 "A".getBytes("ascii"), 370 "AB".getBytes("ascii"), 371 "ABC".getBytes("ascii"), 372 "ABCD".getBytes("ascii"), 373 "ABCDE".getBytes("ascii") 374 }; 375 Base64.Encoder encM = Base64.getMimeEncoder(); 376 Base64.Decoder decM = Base64.getMimeDecoder(); 377 Base64.Encoder enc = Base64.getEncoder(); 378 Base64.Decoder dec = Base64.getDecoder(); 379 for (int i = 0; i < src.length; i++) { 380 // decode(byte[]) 381 byte[] encoded = encM.encode(src[i]); 382 encoded = Arrays.copyOf(encoded, encoded.length + 1); 383 encoded[encoded.length - 1] = nonBase64; 384 checkEqual(decM.decode(encoded), src[i], "Non-base64 char is not ignored"); 385 byte[] decoded = new byte[src[i].length]; 386 decM.decode(encoded, decoded); 387 checkEqual(decoded, src[i], "Non-base64 char is not ignored"); 388 389 try { 390 dec.decode(encoded); 391 throw new RuntimeException("No IAE for non-base64 char"); 392 } catch (IllegalArgumentException iae) {} 393 } 394 } 395 } 396 397 private static void testMalformedPadding() throws Throwable { 398 Object[] data = new Object[] { 399 "$=#", "", 0, // illegal ending unit 400 "A", "", 0, // dangling single byte 401 "A=", "", 0, 402 "A==", "", 0, 403 "QUJDA", "ABC", 4, 404 "QUJDA=", "ABC", 4, 405 "QUJDA==", "ABC", 4, 406 407 "=", "", 0, // unnecessary padding 408 "QUJD=", "ABC", 4, //"ABC".encode() -> "QUJD" 409 410 "AA=", "", 0, // incomplete padding 411 "QQ=", "", 0, 412 "QQ=N", "", 0, // incorrect padding 413 "QQ=?", "", 0, 414 "QUJDQQ=", "ABC", 4, 415 "QUJDQQ=N", "ABC", 4, 416 "QUJDQQ=?", "ABC", 4, 417 }; 418 419 Base64.Decoder[] decs = new Base64.Decoder[] { 420 Base64.getDecoder(), 421 Base64.getUrlDecoder(), 422 Base64.getMimeDecoder() 423 }; 424 425 for (Base64.Decoder dec : decs) { 426 for (int i = 0; i < data.length; i += 3) { 427 final String srcStr = (String)data[i]; 428 final byte[] srcBytes = srcStr.getBytes("ASCII"); 429 final ByteBuffer srcBB = ByteBuffer.wrap(srcBytes); 430 byte[] expected = ((String)data[i + 1]).getBytes("ASCII"); 431 int pos = (Integer)data[i + 2]; 432 433 // decode(byte[]) 434 checkIAE(() -> dec.decode(srcBytes)); 435 436 // decode(String) 437 checkIAE(() -> dec.decode(srcStr)); 438 439 // decode(ByteBuffer) 440 checkIAE(() -> dec.decode(srcBB)); 441 442 // wrap stream 443 checkIOE(new Testable() { 444 public void test() throws IOException { 445 try (InputStream is = dec.wrap(new ByteArrayInputStream(srcBytes))) { 446 while (is.read() != -1); 447 } 448 }}); 449 } 450 } 451 } 452 453 private static void testDecodeUnpadded() throws Throwable { 454 byte[] srcA = new byte[] { 'Q', 'Q' }; 455 byte[] srcAA = new byte[] { 'Q', 'Q', 'E'}; 456 Base64.Decoder dec = Base64.getDecoder(); 457 byte[] ret = dec.decode(srcA); 458 if (ret[0] != 'A') 459 throw new RuntimeException("Decoding unpadding input A failed"); 460 ret = dec.decode(srcAA); 461 if (ret[0] != 'A' && ret[1] != 'A') 462 throw new RuntimeException("Decoding unpadding input AA failed"); 463 ret = new byte[10]; 464 if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 1 && 465 ret[0] != 'A') 466 throw new RuntimeException("Decoding unpadding input A from stream failed"); 467 if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 2 && 468 ret[0] != 'A' && ret[1] != 'A') 469 throw new RuntimeException("Decoding unpadding input AA from stream failed"); 470 } 471 472 // single-non-base64-char should be ignored for mime decoding, but 473 // iae for basic decoding 474 private static void testSingleNonBase64MimeDec() throws Throwable { 475 for (String nonBase64 : new String[] {"#", "(", "!", "\\", "-", "_"}) { 476 if (Base64.getMimeDecoder().decode(nonBase64).length != 0) { 477 throw new RuntimeException("non-base64 char is not ignored"); 478 } 479 try { 480 Base64.getDecoder().decode(nonBase64); 481 throw new RuntimeException("No IAE for single non-base64 char"); 482 } catch (IllegalArgumentException iae) {} 483 } 484 } 485 486 private static final void testEncode(Base64.Encoder enc, ByteBuffer bin, byte[] expected) 487 throws Throwable { 488 489 ByteBuffer bout = enc.encode(bin); 490 byte[] buf = new byte[bout.remaining()]; 491 bout.get(buf); 492 if (bin.hasRemaining()) { 493 throw new RuntimeException( 494 "Base64 enc.encode(ByteBuffer) failed!"); 495 } 496 checkEqual(buf, expected, "Base64 enc.encode(bf, bf) failed!"); 497 } 498 499 private static final void testDecode(Base64.Decoder dec, ByteBuffer bin, byte[] expected) 500 throws Throwable { 501 502 ByteBuffer bout = dec.decode(bin); 503 byte[] buf = new byte[bout.remaining()]; 504 bout.get(buf); 505 checkEqual(buf, expected, "Base64 dec.decode(bf) failed!"); 506 } 507 508 private static final void checkEqual(int v1, int v2, String msg) 509 throws Throwable { 510 if (v1 != v2) { 511 System.out.printf(" v1=%d%n", v1); 512 System.out.printf(" v2=%d%n", v2); 513 throw new RuntimeException(msg); 514 } 515 } 516 517 private static final void checkEqual(byte[] r1, byte[] r2, String msg) 518 throws Throwable { 519 if (!Arrays.equals(r1, r2)) { 520 System.out.printf(" r1[%d]=[%s]%n", r1.length, new String(r1)); 521 System.out.printf(" r2[%d]=[%s]%n", r2.length, new String(r2)); 522 throw new RuntimeException(msg); 523 } 524 } 525 526 // remove line feeds, 527 private static final byte[] normalize(byte[] src) { 528 int n = 0; 529 boolean hasUrl = false; 530 for (int i = 0; i < src.length; i++) { 531 if (src[i] == '\r' || src[i] == '\n') 532 n++; 533 if (src[i] == '-' || src[i] == '_') 534 hasUrl = true; 535 } 536 if (n == 0 && hasUrl == false) 537 return src; 538 byte[] ret = new byte[src.length - n]; 539 int j = 0; 540 for (int i = 0; i < src.length; i++) { 541 if (src[i] == '-') 542 ret[j++] = '+'; 543 else if (src[i] == '_') 544 ret[j++] = '/'; 545 else if (src[i] != '\r' && src[i] != '\n') 546 ret[j++] = src[i]; 547 } 548 return ret; 549 } 550 551 private static void testEncoderKeepsSilence(Base64.Encoder enc) 552 throws Throwable { 553 List<Integer> vals = Arrays.asList(Integer.MIN_VALUE, 554 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111, 555 Integer.MAX_VALUE - 1, Integer.MAX_VALUE, 556 rnd.nextInt(), rnd.nextInt(), rnd.nextInt(), 557 rnd.nextInt()); 558 byte[] buf = new byte[] {1, 0, 91}; 559 for (int off : vals) { 560 for (int len : vals) { 561 if (off >= 0 && len >= 0 && off <= buf.length - len) { 562 // valid args, skip them 563 continue; 564 } 565 // invalid args, test them 566 System.out.println("testing off=" + off + ", len=" + len); 567 568 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 569 try (OutputStream os = enc.wrap(baos)) { 570 os.write(buf, off, len); 571 throw new RuntimeException("Expected IOOBEx was not thrown"); 572 } catch (IndexOutOfBoundsException expected) { 573 } 574 if (baos.size() > 0) 575 throw new RuntimeException("No output was expected, but got " 576 + baos.size() + " bytes"); 577 } 578 } 579 } 580 581 private static void testDecoderKeepsAbstinence(Base64.Decoder dec) 582 throws Throwable { 583 List<Integer> vals = Arrays.asList(Integer.MIN_VALUE, 584 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111, 585 Integer.MAX_VALUE - 1, Integer.MAX_VALUE, 586 rnd.nextInt(), rnd.nextInt(), rnd.nextInt(), 587 rnd.nextInt()); 588 byte[] buf = new byte[3]; 589 for (int off : vals) { 590 for (int len : vals) { 591 if (off >= 0 && len >= 0 && off <= buf.length - len) { 592 // valid args, skip them 593 continue; 594 } 595 // invalid args, test them 596 System.out.println("testing off=" + off + ", len=" + len); 597 598 String input = "AAAAAAAAAAAAAAAAAAAAAA"; 599 ByteArrayInputStream bais = 600 new ByteArrayInputStream(input.getBytes("Latin1")); 601 try (InputStream is = dec.wrap(bais)) { 602 is.read(buf, off, len); 603 throw new RuntimeException("Expected IOOBEx was not thrown"); 604 } catch (IndexOutOfBoundsException expected) { 605 } 606 if (bais.available() != input.length()) 607 throw new RuntimeException("No input should be consumed, " 608 + "but consumed " + (input.length() - bais.available()) 609 + " bytes"); 610 } 611 } 612 } 613 }