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