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