1 /* 2 * Copyright (c) 2012, 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import java.io.FilterOutputStream; 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.OutputStream; 32 import java.nio.ByteBuffer; 33 import java.nio.charset.StandardCharsets; 34 import jdk.internal.HotSpotIntrinsicCandidate; 35 36 /** 37 * This class consists exclusively of static methods for obtaining 38 * encoders and decoders for the Base64 encoding scheme. The 39 * implementation of this class supports the following types of Base64 40 * as specified in 41 * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and 42 * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. 43 * 44 * <ul> 45 * <li><a id="basic"><b>Basic</b></a> 46 * <p> Uses "The Base64 Alphabet" as specified in Table 1 of 47 * RFC 4648 and RFC 2045 for encoding and decoding operation. 48 * The encoder does not add any line feed (line separator) 49 * character. The decoder rejects data that contains characters 50 * outside the base64 alphabet.</p></li> 51 * 52 * <li><a id="url"><b>URL and Filename safe</b></a> 53 * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified 54 * in Table 2 of RFC 4648 for encoding and decoding. The 55 * encoder does not add any line feed (line separator) character. 56 * The decoder rejects data that contains characters outside the 57 * base64 alphabet.</p></li> 58 * 59 * <li><a id="mime"><b>MIME</b></a> 60 * <p> Uses "The Base64 Alphabet" as specified in Table 1 of 61 * RFC 2045 for encoding and decoding operation. The encoded output 62 * must be represented in lines of no more than 76 characters each 63 * and uses a carriage return {@code '\r'} followed immediately by 64 * a linefeed {@code '\n'} as the line separator. No line separator 65 * is added to the end of the encoded output. All line separators 66 * or other characters not found in the base64 alphabet table are 67 * ignored in decoding operation.</p></li> 68 * </ul> 69 * 70 * <p> Unless otherwise noted, passing a {@code null} argument to a 71 * method of this class will cause a {@link java.lang.NullPointerException 72 * NullPointerException} to be thrown. 73 * 74 * @author Xueming Shen 75 * @since 1.8 76 */ 77 78 public class Base64 { 79 80 private Base64() {} 81 82 /** 83 * Returns a {@link Encoder} that encodes using the 84 * <a href="#basic">Basic</a> type base64 encoding scheme. 85 * 86 * @return A Base64 encoder. 87 */ 88 public static Encoder getEncoder() { 89 return Encoder.RFC4648; 90 } 91 92 /** 93 * Returns a {@link Encoder} that encodes using the 94 * <a href="#url">URL and Filename safe</a> type base64 95 * encoding scheme. 96 * 97 * @return A Base64 encoder. 98 */ 99 public static Encoder getUrlEncoder() { 100 return Encoder.RFC4648_URLSAFE; 101 } 102 103 /** 104 * Returns a {@link Encoder} that encodes using the 105 * <a href="#mime">MIME</a> type base64 encoding scheme. 106 * 107 * @return A Base64 encoder. 108 */ 109 public static Encoder getMimeEncoder() { 110 return Encoder.RFC2045; 111 } 112 113 /** 114 * Returns a {@link Encoder} that encodes using the 115 * <a href="#mime">MIME</a> type base64 encoding scheme 116 * with specified line length and line separators. 117 * 118 * @param lineLength 119 * the length of each output line (rounded down to nearest multiple 120 * of 4). If the rounded down line length is not a positive value, 121 * the output will not be separated in lines 122 * @param lineSeparator 123 * the line separator for each output line 124 * 125 * @return A Base64 encoder. 126 * 127 * @throws IllegalArgumentException if {@code lineSeparator} includes any 128 * character of "The Base64 Alphabet" as specified in Table 1 of 129 * RFC 2045. 130 */ 131 public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { 132 Objects.requireNonNull(lineSeparator); 133 int[] base64 = Decoder.fromBase64; 134 for (byte b : lineSeparator) { 135 if (base64[b & 0xff] != -1) 136 throw new IllegalArgumentException( 137 "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); 138 } 139 // round down to nearest multiple of 4 140 lineLength &= ~0b11; 141 if (lineLength <= 0) { 142 return Encoder.RFC4648; 143 } 144 return new Encoder(false, lineSeparator, lineLength, true); 145 } 146 147 /** 148 * Returns a {@link Decoder} that decodes using the 149 * <a href="#basic">Basic</a> type base64 encoding scheme. 150 * 151 * @return A Base64 decoder. 152 */ 153 public static Decoder getDecoder() { 154 return Decoder.RFC4648; 155 } 156 157 /** 158 * Returns a {@link Decoder} that decodes using the 159 * <a href="#url">URL and Filename safe</a> type base64 160 * encoding scheme. 161 * 162 * @return A Base64 decoder. 163 */ 164 public static Decoder getUrlDecoder() { 165 return Decoder.RFC4648_URLSAFE; 166 } 167 168 /** 169 * Returns a {@link Decoder} that decodes using the 170 * <a href="#mime">MIME</a> type base64 decoding scheme. 171 * 172 * @return A Base64 decoder. 173 */ 174 public static Decoder getMimeDecoder() { 175 return Decoder.RFC2045; 176 } 177 178 /** 179 * This class implements an encoder for encoding byte data using 180 * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 181 * 182 * <p> Instances of {@link Encoder} class are safe for use by 183 * multiple concurrent threads. 184 * 185 * <p> Unless otherwise noted, passing a {@code null} argument to 186 * a method of this class will cause a 187 * {@link java.lang.NullPointerException NullPointerException} to 188 * be thrown. 189 * <p> If the encoded byte output of the needed size can not 190 * be allocated, the encode methods of this class will 191 * cause an {@link java.lang.OutOfMemoryError OutOfMemoryError} 192 * to be thrown. 193 * 194 * @see Decoder 195 * @since 1.8 196 */ 197 public static class Encoder { 198 199 private final byte[] newline; 200 private final int linemax; 201 private final boolean isURL; 202 private final boolean doPadding; 203 204 private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { 205 this.isURL = isURL; 206 this.newline = newline; 207 this.linemax = linemax; 208 this.doPadding = doPadding; 209 } 210 211 /** 212 * This array is a lookup table that translates 6-bit positive integer 213 * index values into their "Base64 Alphabet" equivalents as specified 214 * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). 215 */ 216 private static final char[] toBase64 = { 217 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 218 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 219 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 220 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 221 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 222 }; 223 224 /** 225 * It's the lookup table for "URL and Filename safe Base64" as specified 226 * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and 227 * '_'. This table is used when BASE64_URL is specified. 228 */ 229 private static final char[] toBase64URL = { 230 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 231 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 232 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 233 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 234 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' 235 }; 236 237 private static final int MIMELINEMAX = 76; 238 private static final byte[] CRLF = new byte[] {'\r', '\n'}; 239 240 static final Encoder RFC4648 = new Encoder(false, null, -1, true); 241 static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); 242 static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); 243 244 /** 245 * Calculates the length of the encoded output bytes. 246 * 247 * @param srclen length of the bytes to encode 248 * @param throwOOME if true, throws OutOfMemoryError if the length of 249 * the encoded bytes overflows; else returns the 250 * length 251 * @return length of the encoded bytes, or -1 if the length overflows 252 * 253 */ 254 private final int outLength(int srclen, boolean throwOOME) { 255 int len = 0; 256 try { 257 if (doPadding) { 258 len = Math.multiplyExact(4, (Math.addExact(srclen, 2) / 3)); 259 } else { 260 int n = srclen % 3; 261 len = Math.addExact(Math.multiplyExact(4, (srclen / 3)), (n == 0 ? 0 : n + 1)); 262 } 263 if (linemax > 0) { // line separators 264 len = Math.addExact(len, (len - 1) / linemax * newline.length); 265 } 266 } catch (ArithmeticException ex) { 267 if (throwOOME) { 268 throw new OutOfMemoryError("Encoded size is too large"); 269 } else { 270 // let the caller know that encoded bytes length 271 // is too large 272 len = -1; 273 } 274 } 275 return len; 276 } 277 278 /** 279 * Encodes all bytes from the specified byte array into a newly-allocated 280 * byte array using the {@link Base64} encoding scheme. The returned byte 281 * array is of the length of the resulting bytes. 282 * 283 * @param src 284 * the byte array to encode 285 * @return A newly-allocated byte array containing the resulting 286 * encoded bytes. 287 */ 288 public byte[] encode(byte[] src) { 289 int len = outLength(src.length, true); // dst array size 290 byte[] dst = new byte[len]; 291 int ret = encode0(src, 0, src.length, dst); 292 if (ret != dst.length) 293 return Arrays.copyOf(dst, ret); 294 return dst; 295 } 296 297 /** 298 * Encodes all bytes from the specified byte array using the 299 * {@link Base64} encoding scheme, writing the resulting bytes to the 300 * given output byte array, starting at offset 0. 301 * 302 * <p> It is the responsibility of the invoker of this method to make 303 * sure the output byte array {@code dst} has enough space for encoding 304 * all bytes from the input byte array. No bytes will be written to the 305 * output byte array if the output byte array is not big enough. 306 * 307 * @param src 308 * the byte array to encode 309 * @param dst 310 * the output byte array 311 * @return The number of bytes written to the output byte array 312 * 313 * @throws IllegalArgumentException if {@code dst} does not have enough 314 * space for encoding all input bytes. 315 */ 316 public int encode(byte[] src, byte[] dst) { 317 int len = outLength(src.length, false); // dst array size 318 if (dst.length < len || len == -1) 319 throw new IllegalArgumentException( 320 "Output byte array is too small for encoding all input bytes"); 321 return encode0(src, 0, src.length, dst); 322 } 323 324 /** 325 * Encodes the specified byte array into a String using the {@link Base64} 326 * encoding scheme. 327 * 328 * <p> This method first encodes all input bytes into a base64 encoded 329 * byte array and then constructs a new String by using the encoded byte 330 * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 331 * ISO-8859-1} charset. 332 * 333 * <p> In other words, an invocation of this method has exactly the same 334 * effect as invoking 335 * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. 336 * 337 * @param src 338 * the byte array to encode 339 * @return A String containing the resulting Base64 encoded characters 340 */ 341 @SuppressWarnings("deprecation") 342 public String encodeToString(byte[] src) { 343 byte[] encoded = encode(src); 344 return new String(encoded, 0, 0, encoded.length); 345 } 346 347 /** 348 * Encodes all remaining bytes from the specified byte buffer into 349 * a newly-allocated ByteBuffer using the {@link Base64} encoding 350 * scheme. 351 * 352 * Upon return, the source buffer's position will be updated to 353 * its limit; its limit will not have been changed. The returned 354 * output buffer's position will be zero and its limit will be the 355 * number of resulting encoded bytes. 356 * 357 * @param buffer 358 * the source ByteBuffer to encode 359 * @return A newly-allocated byte buffer containing the encoded bytes. 360 */ 361 public ByteBuffer encode(ByteBuffer buffer) { 362 int len = outLength(buffer.remaining(), true); 363 byte[] dst = new byte[len]; 364 int ret = 0; 365 if (buffer.hasArray()) { 366 ret = encode0(buffer.array(), 367 buffer.arrayOffset() + buffer.position(), 368 buffer.arrayOffset() + buffer.limit(), 369 dst); 370 buffer.position(buffer.limit()); 371 } else { 372 byte[] src = new byte[buffer.remaining()]; 373 buffer.get(src); 374 ret = encode0(src, 0, src.length, dst); 375 } 376 if (ret != dst.length) 377 dst = Arrays.copyOf(dst, ret); 378 return ByteBuffer.wrap(dst); 379 } 380 381 /** 382 * Wraps an output stream for encoding byte data using the {@link Base64} 383 * encoding scheme. 384 * 385 * <p> It is recommended to promptly close the returned output stream after 386 * use, during which it will flush all possible leftover bytes to the underlying 387 * output stream. Closing the returned output stream will close the underlying 388 * output stream. 389 * 390 * @param os 391 * the output stream. 392 * @return the output stream for encoding the byte data into the 393 * specified Base64 encoded format 394 */ 395 public OutputStream wrap(OutputStream os) { 396 Objects.requireNonNull(os); 397 return new EncOutputStream(os, isURL ? toBase64URL : toBase64, 398 newline, linemax, doPadding); 399 } 400 401 /** 402 * Returns an encoder instance that encodes equivalently to this one, 403 * but without adding any padding character at the end of the encoded 404 * byte data. 405 * 406 * <p> The encoding scheme of this encoder instance is unaffected by 407 * this invocation. The returned encoder instance should be used for 408 * non-padding encoding operation. 409 * 410 * @return an equivalent encoder that encodes without adding any 411 * padding character at the end 412 */ 413 public Encoder withoutPadding() { 414 if (!doPadding) 415 return this; 416 return new Encoder(isURL, newline, linemax, false); 417 } 418 419 @HotSpotIntrinsicCandidate 420 private void encodeBlock(byte[] src, int sp, int sl, byte[] dst, int dp, boolean isURL) { 421 char[] base64 = isURL ? toBase64URL : toBase64; 422 for (int sp0 = sp, dp0 = dp ; sp0 < sl; ) { 423 int bits = (src[sp0++] & 0xff) << 16 | 424 (src[sp0++] & 0xff) << 8 | 425 (src[sp0++] & 0xff); 426 dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 427 dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 428 dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 429 dst[dp0++] = (byte)base64[bits & 0x3f]; 430 } 431 } 432 433 private int encode0(byte[] src, int off, int end, byte[] dst) { 434 char[] base64 = isURL ? toBase64URL : toBase64; 435 int sp = off; 436 int slen = (end - off) / 3 * 3; 437 int sl = off + slen; 438 if (linemax > 0 && slen > linemax / 4 * 3) 439 slen = linemax / 4 * 3; 440 int dp = 0; 441 while (sp < sl) { 442 int sl0 = Math.min(sp + slen, sl); 443 encodeBlock(src, sp, sl0, dst, dp, isURL); 444 int dlen = (sl0 - sp) / 3 * 4; 445 dp += dlen; 446 sp = sl0; 447 if (dlen == linemax && sp < end) { 448 for (byte b : newline){ 449 dst[dp++] = b; 450 } 451 } 452 } 453 if (sp < end) { // 1 or 2 leftover bytes 454 int b0 = src[sp++] & 0xff; 455 dst[dp++] = (byte)base64[b0 >> 2]; 456 if (sp == end) { 457 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 458 if (doPadding) { 459 dst[dp++] = '='; 460 dst[dp++] = '='; 461 } 462 } else { 463 int b1 = src[sp++] & 0xff; 464 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 465 dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 466 if (doPadding) { 467 dst[dp++] = '='; 468 } 469 } 470 } 471 return dp; 472 } 473 } 474 475 /** 476 * This class implements a decoder for decoding byte data using the 477 * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 478 * 479 * <p> The Base64 padding character {@code '='} is accepted and 480 * interpreted as the end of the encoded byte data, but is not 481 * required. So if the final unit of the encoded byte data only has 482 * two or three Base64 characters (without the corresponding padding 483 * character(s) padded), they are decoded as if followed by padding 484 * character(s). If there is a padding character present in the 485 * final unit, the correct number of padding character(s) must be 486 * present, otherwise {@code IllegalArgumentException} ( 487 * {@code IOException} when reading from a Base64 stream) is thrown 488 * during decoding. 489 * 490 * <p> Instances of {@link Decoder} class are safe for use by 491 * multiple concurrent threads. 492 * 493 * <p> Unless otherwise noted, passing a {@code null} argument to 494 * a method of this class will cause a 495 * {@link java.lang.NullPointerException NullPointerException} to 496 * be thrown. 497 * <p> If the decoded byte output of the needed size can not 498 * be allocated, the decode methods of this class will 499 * cause an {@link java.lang.OutOfMemoryError OutOfMemoryError} 500 * to be thrown. 501 * 502 * @see Encoder 503 * @since 1.8 504 */ 505 public static class Decoder { 506 507 private final boolean isURL; 508 private final boolean isMIME; 509 510 private Decoder(boolean isURL, boolean isMIME) { 511 this.isURL = isURL; 512 this.isMIME = isMIME; 513 } 514 515 /** 516 * Lookup table for decoding unicode characters drawn from the 517 * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 518 * their 6-bit positive integer equivalents. Characters that 519 * are not in the Base64 alphabet but fall within the bounds of 520 * the array are encoded to -1. 521 * 522 */ 523 private static final int[] fromBase64 = new int[256]; 524 static { 525 Arrays.fill(fromBase64, -1); 526 for (int i = 0; i < Encoder.toBase64.length; i++) 527 fromBase64[Encoder.toBase64[i]] = i; 528 fromBase64['='] = -2; 529 } 530 531 /** 532 * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 533 * as specified in Table2 of the RFC 4648. 534 */ 535 private static final int[] fromBase64URL = new int[256]; 536 537 static { 538 Arrays.fill(fromBase64URL, -1); 539 for (int i = 0; i < Encoder.toBase64URL.length; i++) 540 fromBase64URL[Encoder.toBase64URL[i]] = i; 541 fromBase64URL['='] = -2; 542 } 543 544 static final Decoder RFC4648 = new Decoder(false, false); 545 static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 546 static final Decoder RFC2045 = new Decoder(false, true); 547 548 /** 549 * Decodes all bytes from the input byte array using the {@link Base64} 550 * encoding scheme, writing the results into a newly-allocated output 551 * byte array. The returned byte array is of the length of the resulting 552 * bytes. 553 * 554 * @param src 555 * the byte array to decode 556 * 557 * @return A newly-allocated byte array containing the decoded bytes. 558 * 559 * @throws IllegalArgumentException 560 * if {@code src} is not in valid Base64 scheme 561 */ 562 public byte[] decode(byte[] src) { 563 byte[] dst = new byte[outLength(src, 0, src.length, true)]; 564 int ret = decode0(src, 0, src.length, dst); 565 if (ret != dst.length) { 566 dst = Arrays.copyOf(dst, ret); 567 } 568 return dst; 569 } 570 571 /** 572 * Decodes a Base64 encoded String into a newly-allocated byte array 573 * using the {@link Base64} encoding scheme. 574 * 575 * <p> An invocation of this method has exactly the same effect as invoking 576 * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} 577 * 578 * @param src 579 * the string to decode 580 * 581 * @return A newly-allocated byte array containing the decoded bytes. 582 * 583 * @throws IllegalArgumentException 584 * if {@code src} is not in valid Base64 scheme 585 */ 586 public byte[] decode(String src) { 587 return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 588 } 589 590 /** 591 * Decodes all bytes from the input byte array using the {@link Base64} 592 * encoding scheme, writing the results into the given output byte array, 593 * starting at offset 0. 594 * 595 * <p> It is the responsibility of the invoker of this method to make 596 * sure the output byte array {@code dst} has enough space for decoding 597 * all bytes from the input byte array. No bytes will be written to 598 * the output byte array if the output byte array is not big enough. 599 * 600 * <p> If the input byte array is not in valid Base64 encoding scheme 601 * then some bytes may have been written to the output byte array before 602 * IllegalargumentException is thrown. 603 * 604 * @param src 605 * the byte array to decode 606 * @param dst 607 * the output byte array 608 * 609 * @return The number of bytes written to the output byte array 610 * 611 * @throws IllegalArgumentException 612 * if {@code src} is not in valid Base64 scheme, or {@code dst} 613 * does not have enough space for decoding all input bytes. 614 */ 615 public int decode(byte[] src, byte[] dst) { 616 int len = outLength(src, 0, src.length, false); 617 if (dst.length < len || len == -1) 618 throw new IllegalArgumentException( 619 "Output byte array is too small for decoding all input bytes"); 620 return decode0(src, 0, src.length, dst); 621 } 622 623 /** 624 * Decodes all bytes from the input byte buffer using the {@link Base64} 625 * encoding scheme, writing the results into a newly-allocated ByteBuffer. 626 * 627 * <p> Upon return, the source buffer's position will be updated to 628 * its limit; its limit will not have been changed. The returned 629 * output buffer's position will be zero and its limit will be the 630 * number of resulting decoded bytes 631 * 632 * <p> {@code IllegalArgumentException} is thrown if the input buffer 633 * is not in valid Base64 encoding scheme. The position of the input 634 * buffer will not be advanced in this case. 635 * 636 * @param buffer 637 * the ByteBuffer to decode 638 * 639 * @return A newly-allocated byte buffer containing the decoded bytes 640 * 641 * @throws IllegalArgumentException 642 * if {@code buffer} is not in valid Base64 scheme 643 */ 644 public ByteBuffer decode(ByteBuffer buffer) { 645 int pos0 = buffer.position(); 646 try { 647 byte[] src; 648 int sp, sl; 649 if (buffer.hasArray()) { 650 src = buffer.array(); 651 sp = buffer.arrayOffset() + buffer.position(); 652 sl = buffer.arrayOffset() + buffer.limit(); 653 buffer.position(buffer.limit()); 654 } else { 655 src = new byte[buffer.remaining()]; 656 buffer.get(src); 657 sp = 0; 658 sl = src.length; 659 } 660 byte[] dst = new byte[outLength(src, sp, sl, true)]; 661 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 662 } catch (IllegalArgumentException iae) { 663 buffer.position(pos0); 664 throw iae; 665 } 666 } 667 668 /** 669 * Returns an input stream for decoding {@link Base64} encoded byte stream. 670 * 671 * <p> The {@code read} methods of the returned {@code InputStream} will 672 * throw {@code IOException} when reading bytes that cannot be decoded. 673 * 674 * <p> Closing the returned input stream will close the underlying 675 * input stream. 676 * 677 * @param is 678 * the input stream 679 * 680 * @return the input stream for decoding the specified Base64 encoded 681 * byte stream 682 */ 683 public InputStream wrap(InputStream is) { 684 Objects.requireNonNull(is); 685 return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 686 } 687 688 /** 689 * Calculates the length of the decoded output bytes. 690 * 691 * @param src the byte array to decode 692 * @param sp the source position 693 * @param sl the source limit 694 * @param throwOOME if true, throws OutOfMemoryError if the length of 695 * the decoded bytes overflows; else returns the 696 * length 697 * @return length of the decoded bytes, or -1 if the length overflows 698 * 699 */ 700 private int outLength(byte[] src, int sp, int sl, boolean throwOOME) { 701 int[] base64 = isURL ? fromBase64URL : fromBase64; 702 int paddings = 0; 703 int len = sl - sp; 704 if (len == 0) 705 return 0; 706 if (len < 2) { 707 if (isMIME && base64[0] == -1) 708 return 0; 709 throw new IllegalArgumentException( 710 "Input byte[] should at least have 2 bytes for base64 bytes"); 711 } 712 if (isMIME) { 713 // scan all bytes to fill out all non-alphabet. a performance 714 // trade-off of pre-scan or Arrays.copyOf 715 int n = 0; 716 while (sp < sl) { 717 int b = src[sp++] & 0xff; 718 if (b == '=') { 719 len -= (sl - sp + 1); 720 break; 721 } 722 if ((b = base64[b]) == -1) 723 n++; 724 } 725 len -= n; 726 } else { 727 if (src[sl - 1] == '=') { 728 paddings++; 729 if (src[sl - 2] == '=') 730 paddings++; 731 } 732 } 733 if (paddings == 0 && (len & 0x3) != 0) 734 paddings = 4 - (len & 0x3); 735 736 try { 737 len = Math.multiplyExact(3, (Math.addExact(len, 3) / 4)) - paddings; 738 } catch (ArithmeticException ex) { 739 if (throwOOME) { 740 throw new OutOfMemoryError("Decoded size is too large"); 741 } else { 742 // let the caller know that the decoded bytes length 743 // is too large 744 len = -1; 745 } 746 } 747 return len; 748 } 749 750 private int decode0(byte[] src, int sp, int sl, byte[] dst) { 751 int[] base64 = isURL ? fromBase64URL : fromBase64; 752 int dp = 0; 753 int bits = 0; 754 int shiftto = 18; // pos of first byte of 4-byte atom 755 756 while (sp < sl) { 757 if (shiftto == 18 && sp + 4 < sl) { // fast path 758 int sl0 = sp + ((sl - sp) & ~0b11); 759 while (sp < sl0) { 760 int b1 = base64[src[sp++] & 0xff]; 761 int b2 = base64[src[sp++] & 0xff]; 762 int b3 = base64[src[sp++] & 0xff]; 763 int b4 = base64[src[sp++] & 0xff]; 764 if ((b1 | b2 | b3 | b4) < 0) { // non base64 byte 765 sp -= 4; 766 break; 767 } 768 int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4; 769 dst[dp++] = (byte)(bits0 >> 16); 770 dst[dp++] = (byte)(bits0 >> 8); 771 dst[dp++] = (byte)(bits0); 772 } 773 if (sp >= sl) 774 break; 775 } 776 int b = src[sp++] & 0xff; 777 if ((b = base64[b]) < 0) { 778 if (b == -2) { // padding byte '=' 779 // = shiftto==18 unnecessary padding 780 // x= shiftto==12 a dangling single x 781 // x to be handled together with non-padding case 782 // xx= shiftto==6&&sp==sl missing last = 783 // xx=y shiftto==6 last is not = 784 if (shiftto == 6 && (sp == sl || src[sp++] != '=') || 785 shiftto == 18) { 786 throw new IllegalArgumentException( 787 "Input byte array has wrong 4-byte ending unit"); 788 } 789 break; 790 } 791 if (isMIME) // skip if for rfc2045 792 continue; 793 else 794 throw new IllegalArgumentException( 795 "Illegal base64 character " + 796 Integer.toString(src[sp - 1], 16)); 797 } 798 bits |= (b << shiftto); 799 shiftto -= 6; 800 if (shiftto < 0) { 801 dst[dp++] = (byte)(bits >> 16); 802 dst[dp++] = (byte)(bits >> 8); 803 dst[dp++] = (byte)(bits); 804 shiftto = 18; 805 bits = 0; 806 } 807 } 808 // reached end of byte array or hit padding '=' characters. 809 if (shiftto == 6) { 810 dst[dp++] = (byte)(bits >> 16); 811 } else if (shiftto == 0) { 812 dst[dp++] = (byte)(bits >> 16); 813 dst[dp++] = (byte)(bits >> 8); 814 } else if (shiftto == 12) { 815 // dangling single "x", incorrectly encoded. 816 throw new IllegalArgumentException( 817 "Last unit does not have enough valid bits"); 818 } 819 // anything left is invalid, if is not MIME. 820 // if MIME, ignore all non-base64 character 821 while (sp < sl) { 822 if (isMIME && base64[src[sp++] & 0xff] < 0) 823 continue; 824 throw new IllegalArgumentException( 825 "Input byte array has incorrect ending byte at " + sp); 826 } 827 return dp; 828 } 829 } 830 831 /* 832 * An output stream for encoding bytes into the Base64. 833 */ 834 private static class EncOutputStream extends FilterOutputStream { 835 836 private int leftover = 0; 837 private int b0, b1, b2; 838 private boolean closed = false; 839 840 private final char[] base64; // byte->base64 mapping 841 private final byte[] newline; // line separator, if needed 842 private final int linemax; 843 private final boolean doPadding;// whether or not to pad 844 private int linepos = 0; 845 private byte[] buf; 846 847 EncOutputStream(OutputStream os, char[] base64, 848 byte[] newline, int linemax, boolean doPadding) { 849 super(os); 850 this.base64 = base64; 851 this.newline = newline; 852 this.linemax = linemax; 853 this.doPadding = doPadding; 854 this.buf = new byte[linemax <= 0 ? 8124 : linemax]; 855 } 856 857 @Override 858 public void write(int b) throws IOException { 859 byte[] buf = new byte[1]; 860 buf[0] = (byte)(b & 0xff); 861 write(buf, 0, 1); 862 } 863 864 private void checkNewline() throws IOException { 865 if (linepos == linemax) { 866 out.write(newline); 867 linepos = 0; 868 } 869 } 870 871 private void writeb4(char b1, char b2, char b3, char b4) throws IOException { 872 buf[0] = (byte)b1; 873 buf[1] = (byte)b2; 874 buf[2] = (byte)b3; 875 buf[3] = (byte)b4; 876 out.write(buf, 0, 4); 877 } 878 879 @Override 880 public void write(byte[] b, int off, int len) throws IOException { 881 if (closed) 882 throw new IOException("Stream is closed"); 883 if (off < 0 || len < 0 || len > b.length - off) 884 throw new ArrayIndexOutOfBoundsException(); 885 if (len == 0) 886 return; 887 if (leftover != 0) { 888 if (leftover == 1) { 889 b1 = b[off++] & 0xff; 890 len--; 891 if (len == 0) { 892 leftover++; 893 return; 894 } 895 } 896 b2 = b[off++] & 0xff; 897 len--; 898 checkNewline(); 899 writeb4(base64[b0 >> 2], 900 base64[(b0 << 4) & 0x3f | (b1 >> 4)], 901 base64[(b1 << 2) & 0x3f | (b2 >> 6)], 902 base64[b2 & 0x3f]); 903 linepos += 4; 904 } 905 int nBits24 = len / 3; 906 leftover = len - (nBits24 * 3); 907 908 while (nBits24 > 0) { 909 checkNewline(); 910 int dl = linemax <= 0 ? buf.length : buf.length - linepos; 911 int sl = off + Math.min(nBits24, dl / 4) * 3; 912 int dp = 0; 913 for (int sp = off; sp < sl; ) { 914 int bits = (b[sp++] & 0xff) << 16 | 915 (b[sp++] & 0xff) << 8 | 916 (b[sp++] & 0xff); 917 buf[dp++] = (byte)base64[(bits >>> 18) & 0x3f]; 918 buf[dp++] = (byte)base64[(bits >>> 12) & 0x3f]; 919 buf[dp++] = (byte)base64[(bits >>> 6) & 0x3f]; 920 buf[dp++] = (byte)base64[bits & 0x3f]; 921 } 922 out.write(buf, 0, dp); 923 off = sl; 924 linepos += dp; 925 nBits24 -= dp / 4; 926 } 927 if (leftover == 1) { 928 b0 = b[off++] & 0xff; 929 } else if (leftover == 2) { 930 b0 = b[off++] & 0xff; 931 b1 = b[off++] & 0xff; 932 } 933 } 934 935 @Override 936 public void close() throws IOException { 937 if (!closed) { 938 closed = true; 939 if (leftover == 1) { 940 checkNewline(); 941 out.write(base64[b0 >> 2]); 942 out.write(base64[(b0 << 4) & 0x3f]); 943 if (doPadding) { 944 out.write('='); 945 out.write('='); 946 } 947 } else if (leftover == 2) { 948 checkNewline(); 949 out.write(base64[b0 >> 2]); 950 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 951 out.write(base64[(b1 << 2) & 0x3f]); 952 if (doPadding) { 953 out.write('='); 954 } 955 } 956 leftover = 0; 957 out.close(); 958 } 959 } 960 } 961 962 /* 963 * An input stream for decoding Base64 bytes 964 */ 965 private static class DecInputStream extends InputStream { 966 967 private final InputStream is; 968 private final boolean isMIME; 969 private final int[] base64; // base64 -> byte mapping 970 private int bits = 0; // 24-bit buffer for decoding 971 private int nextin = 18; // next available "off" in "bits" for input; 972 // -> 18, 12, 6, 0 973 private int nextout = -8; // next available "off" in "bits" for output; 974 // -> 8, 0, -8 (no byte for output) 975 private boolean eof = false; 976 private boolean closed = false; 977 978 DecInputStream(InputStream is, int[] base64, boolean isMIME) { 979 this.is = is; 980 this.base64 = base64; 981 this.isMIME = isMIME; 982 } 983 984 private byte[] sbBuf = new byte[1]; 985 986 @Override 987 public int read() throws IOException { 988 return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 989 } 990 991 private int eof(byte[] b, int off, int len, int oldOff) 992 throws IOException 993 { 994 eof = true; 995 if (nextin != 18) { 996 if (nextin == 12) 997 throw new IOException("Base64 stream has one un-decoded dangling byte."); 998 // treat ending xx/xxx without padding character legal. 999 // same logic as v == '=' below 1000 b[off++] = (byte)(bits >> (16)); 1001 if (nextin == 0) { // only one padding byte 1002 if (len == 1) { // no enough output space 1003 bits >>= 8; // shift to lowest byte 1004 nextout = 0; 1005 } else { 1006 b[off++] = (byte) (bits >> 8); 1007 } 1008 } 1009 } 1010 return off == oldOff ? -1 : off - oldOff; 1011 } 1012 1013 private int padding(byte[] b, int off, int len, int oldOff) 1014 throws IOException 1015 { 1016 // = shiftto==18 unnecessary padding 1017 // x= shiftto==12 dangling x, invalid unit 1018 // xx= shiftto==6 && missing last '=' 1019 // xx=y or last is not '=' 1020 if (nextin == 18 || nextin == 12 || 1021 nextin == 6 && is.read() != '=') { 1022 throw new IOException("Illegal base64 ending sequence:" + nextin); 1023 } 1024 b[off++] = (byte)(bits >> (16)); 1025 if (nextin == 0) { // only one padding byte 1026 if (len == 1) { // no enough output space 1027 bits >>= 8; // shift to lowest byte 1028 nextout = 0; 1029 } else { 1030 b[off++] = (byte) (bits >> 8); 1031 } 1032 } 1033 eof = true; 1034 return off - oldOff; 1035 } 1036 1037 @Override 1038 public int read(byte[] b, int off, int len) throws IOException { 1039 if (closed) 1040 throw new IOException("Stream is closed"); 1041 if (eof && nextout < 0) // eof and no leftover 1042 return -1; 1043 if (off < 0 || len < 0 || len > b.length - off) 1044 throw new IndexOutOfBoundsException(); 1045 int oldOff = off; 1046 while (nextout >= 0) { // leftover output byte(s) in bits buf 1047 if (len == 0) 1048 return off - oldOff; 1049 b[off++] = (byte)(bits >> nextout); 1050 len--; 1051 nextout -= 8; 1052 } 1053 bits = 0; 1054 while (len > 0) { 1055 int v = is.read(); 1056 if (v == -1) { 1057 return eof(b, off, len, oldOff); 1058 } 1059 if ((v = base64[v]) < 0) { 1060 if (v == -2) { // padding byte(s) 1061 return padding(b, off, len, oldOff); 1062 } 1063 if (v == -1) { 1064 if (!isMIME) 1065 throw new IOException("Illegal base64 character " + 1066 Integer.toString(v, 16)); 1067 continue; // skip if for rfc2045 1068 } 1069 // neve be here 1070 } 1071 bits |= (v << nextin); 1072 if (nextin == 0) { 1073 nextin = 18; // clear for next in 1074 b[off++] = (byte)(bits >> 16); 1075 if (len == 1) { 1076 nextout = 8; // 2 bytes left in bits 1077 break; 1078 } 1079 b[off++] = (byte)(bits >> 8); 1080 if (len == 2) { 1081 nextout = 0; // 1 byte left in bits 1082 break; 1083 } 1084 b[off++] = (byte)bits; 1085 len -= 3; 1086 bits = 0; 1087 } else { 1088 nextin -= 6; 1089 } 1090 } 1091 return off - oldOff; 1092 } 1093 1094 @Override 1095 public int available() throws IOException { 1096 if (closed) 1097 throw new IOException("Stream is closed"); 1098 return is.available(); // TBD: 1099 } 1100 1101 @Override 1102 public void close() throws IOException { 1103 if (!closed) { 1104 closed = true; 1105 is.close(); 1106 } 1107 } 1108 } 1109 }