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