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