1 /* 2 * Copyright (c) 2012, 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 * <p> 44 * <ul> 45 * <a name="basic"> 46 * <li><b>Basic</b> 47 * <p> Uses "The Base64 Alphabet" as specified in Table 1 of 48 * RFC 4648 and RFC 2045 for encoding and decoding operation. 49 * The encoder does not add any line feed (line separator) 50 * character. The decoder rejects data that contains characters 51 * outside the base64 alphabet.</p></li> 52 * 53 * <a name="url"> 54 * <li><b>URL and Filename safe</b> 55 * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified 56 * in Table 2 of RFC 4648 for encoding and decoding. The 57 * encoder does not add any line feed (line separator) character. 58 * The decoder rejects data that contains characters outside the 59 * base64 alphabet.</p></li> 60 * 61 * <a name="mime"> 62 * <li><b>MIME</b> 63 * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of 64 * RFC 2045 for encoding and decoding operation. The encoded output 65 * must be represented in lines of no more than 76 characters each 66 * and uses a carriage return {@code '\r'} followed immediately by 67 * a linefeed {@code '\n'} as the line separator. All line separators 68 * or other characters not found in the base64 alphabet table are 69 * ignored in decoding operation.</p></li> 70 * </ul> 71 * 72 * <p> Unless otherwise noted, passing a {@code null} argument to a 73 * method of this class will cause a {@link java.lang.NullPointerException 74 * NullPointerException} to be thrown. 75 * 76 * @author Xueming Shen 77 * @since 1.8 78 */ 79 80 public class Base64 { 81 82 private Base64() {} 83 84 /** 85 * Returns a {@link Encoder} that encodes using the 86 * <a href="#basic">Basic</a> type base64 encoding scheme. 87 * 88 * @return A Base64 encoder. 89 */ 90 public static Encoder getEncoder() { 91 return Encoder.RFC4648; 92 } 93 94 /** 95 * Returns a {@link Encoder} that encodes using the 96 * <a href="#url">URL and Filename safe</a> type base64 97 * encoding scheme. 98 * 99 * @return A Base64 encoder. 100 */ 101 public static Encoder getUrlEncoder() { 102 return Encoder.RFC4648_URLSAFE; 103 } 104 105 /** 106 * Returns a {@link Encoder} that encodes using the 107 * <a href="#mime">MIME</a> type base64 encoding scheme. 108 * 109 * @return A Base64 encoder. 110 */ 111 public static Encoder getMimeEncoder() { 112 return Encoder.RFC2045; 113 } 114 115 /** 116 * Returns a {@link Encoder} that encodes using the 117 * <a href="#mime">MIME</a> type base64 encoding scheme 118 * with specified line length and line separators. 119 * 120 * @param lineLength 121 * the length of each output line (rounded down to nearest multiple 122 * of 4). If {@code lineLength <= 0} the output will not be separated 123 * in lines 124 * @param lineSeparator 125 * the line separator for each output line 126 * 127 * @return A Base64 encoder. 128 * 129 * @throws IllegalArgumentException if {@code lineSeparator} includes any 130 * character of "The Base64 Alphabet" as specified in Table 1 of 131 * RFC 2045. 132 */ 133 public static Encoder getEncoder(int lineLength, byte[] lineSeparator) { 134 Objects.requireNonNull(lineSeparator); 135 int[] base64 = Decoder.fromBase64; 136 for (byte b : lineSeparator) { 137 if (base64[b & 0xff] != -1) 138 throw new IllegalArgumentException( 139 "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); 140 } 141 return new Encoder(false, lineSeparator, lineLength >> 2 << 2); 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 */ 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 */ 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 */ 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 196 private Encoder(boolean isURL, byte[] newline, int linemax) { 197 this.isURL = isURL; 198 this.newline = newline; 199 this.linemax = linemax; 200 } 201 202 /** 203 * This array is a lookup table that translates 6-bit positive integer 204 * index values into their "Base64 Alphabet" equivalents as specified 205 * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). 206 */ 207 private static final char[] toBase64 = { 208 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 209 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 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 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 213 }; 214 215 /** 216 * It's the lookup table for "URL and Filename safe Base64" as specified 217 * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and 218 * '_'. This table is used when BASE64_URL is specified. 219 */ 220 private static final char[] toBase64URL = { 221 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 222 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 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 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' 226 }; 227 228 private static final int MIMELINEMAX = 76; 229 private static final byte[] CRLF = new byte[] {'\r', '\n'}; 230 231 static final Encoder RFC4648 = new Encoder(false, null, -1); 232 static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1); 233 static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX); 234 235 /** 236 * Encodes all bytes from the specified byte array into a newly-allocated 237 * byte array using the {@link Base64} encoding scheme. The returned byte 238 * array is of the length of the resulting bytes. 239 * 240 * @param src 241 * the byte array to encode 242 * @return A newly-allocated byte array containing the resulting 243 * encoded bytes. 244 */ 245 public byte[] encode(byte[] src) { 246 int len = 4 * ((src.length + 2) / 3); // dst array size 247 if (linemax > 0) // line separators 248 len += (len - 1) / linemax * newline.length; 249 byte[] dst = new byte[len]; 250 int ret = encode0(src, 0, src.length, dst); 251 if (ret != dst.length) 252 return Arrays.copyOf(dst, ret); 253 return dst; 254 } 255 256 /** 257 * Encodes all bytes from the specified byte array using the 258 * {@link Base64} encoding scheme, writing the resulting bytes to the 259 * given output byte array, starting at offset 0. 260 * 261 * <p> It is the responsibility of the invoker of this method to make 262 * sure the output byte array {@code dst} has enough space for encoding 263 * all bytes from the input byte array. No bytes will be written to the 264 * output byte array if the output byte array is not big enough. 265 * 266 * @param src 267 * the byte array to encode 268 * @param dst 269 * the output byte array 270 * @return The number of bytes written to the output byte array 271 * 272 * @throws IllegalArgumentException if {@code dst} does not have enough 273 * space for encoding all input bytes. 274 */ 275 public int encode(byte[] src, byte[] dst) { 276 int len = 4 * ((src.length + 2) / 3); // dst array size 277 if (linemax > 0) { 278 len += (len - 1) / linemax * newline.length; 279 } 280 if (dst.length < len) 281 throw new IllegalArgumentException( 282 "Output byte array is too small for encoding all input bytes"); 283 return encode0(src, 0, src.length, dst); 284 } 285 286 /** 287 * Encodes the specified byte array into a String using the {@link Base64} 288 * encoding scheme. 289 * 290 * <p> This method first encodes all input bytes into a base64 encoded 291 * byte array and then constructs a new String by using the encoded byte 292 * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 293 * ISO-8859-1} charset. 294 * 295 * <p> In other words, an invocation of this method has exactly the same 296 * effect as invoking 297 * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. 298 * 299 * @param src 300 * the byte array to encode 301 * @return A String containing the resulting Base64 encoded characters 302 */ 303 @SuppressWarnings("deprecation") 304 public String encodeToString(byte[] src) { 305 byte[] encoded = encode(src); 306 return new String(encoded, 0, 0, encoded.length); 307 } 308 309 /** 310 * Encodes all remaining bytes from the specified byte buffer into 311 * a newly-allocated ByteBuffer using the {@link Base64} encoding 312 * scheme. 313 * 314 * Upon return, the source buffer's position will be updated to 315 * its limit; its limit will not have been changed. The returned 316 * output buffer's position will be zero and its limit will be the 317 * number of resulting encoded bytes. 318 * 319 * @param buffer 320 * the source ByteBuffer to encode 321 * @return A newly-allocated byte buffer containing the encoded bytes. 322 */ 323 public ByteBuffer encode(ByteBuffer buffer) { 324 int len = 4 * ((buffer.remaining() + 2) / 3); 325 if (linemax > 0) 326 len += (len - 1) / linemax * newline.length; 327 byte[] dst = new byte[len]; 328 int ret = 0; 329 if (buffer.hasArray()) { 330 ret = encode0(buffer.array(), 331 buffer.arrayOffset() + buffer.position(), 332 buffer.arrayOffset() + buffer.limit(), 333 dst); 334 buffer.position(buffer.limit()); 335 } else { 336 byte[] src = new byte[buffer.remaining()]; 337 buffer.get(src); 338 ret = encode0(src, 0, src.length, dst); 339 } 340 if (ret != dst.length) 341 dst = Arrays.copyOf(dst, ret); 342 return ByteBuffer.wrap(dst); 343 } 344 345 /** 346 * Encodes as many bytes as possible from the input byte buffer 347 * using the {@link Base64} encoding scheme, writing the resulting 348 * bytes to the given output byte buffer. 349 * 350 * <p>The buffers are read from, and written to, starting at their 351 * current positions. Upon return, the input and output buffers' 352 * positions will be advanced to reflect the bytes read and written, 353 * but their limits will not be modified. 354 * 355 * <p>The encoding operation will stop and return if either all 356 * remaining bytes in the input buffer have been encoded and written 357 * to the output buffer, or the output buffer has insufficient space 358 * to encode any more input bytes. The encoding operation can be 359 * continued, if there is more bytes in input buffer to be encoded, 360 * by invoking this method again with an output buffer that has more 361 * {@linkplain java.nio.Buffer#remaining remaining} bytes. This is 362 * typically done by draining any encoded bytes from the output buffer. 363 * The value returned from last invocation needs to be passed in as the 364 * third parameter {@code bytesOut} if it is to continue an unfinished 365 * encoding, 0 otherwise. 366 * 367 * <p><b>Recommended Usage Example</b> 368 * <pre> 369 * ByteBuffer src = ...; 370 * ByteBuffer dst = ...; 371 * Base64.Encoder enc = Base64.getMimeDecoder(); 372 * 373 * int bytesOut = 0; 374 * while (src.hasRemaining()) { 375 * // clear output buffer for decoding 376 * dst.clear(); 377 * bytesOut = enc.encode(src, dst, bytesOut); 378 * 379 * // read encoded bytes out of "dst" 380 * dst.flip(); 381 * ... 382 * } 383 * </pre> 384 * 385 * @param src 386 * the input byte buffer to encode 387 * @param dst 388 * the output byte buffer 389 * @param bytesOut 390 * the return value of last invocation if this is to continue 391 * an unfinished encoding operation, 0 otherwise 392 * @return The sum total of {@code bytesOut} and the number of bytes 393 * written to the output ByteBuffer during this invocation. 394 */ 395 public int encode(ByteBuffer src, ByteBuffer dst, int bytesOut) { 396 if (src.hasArray() && dst.hasArray()) 397 return encodeArray(src, dst, bytesOut); 398 return encodeBuffer(src, dst, bytesOut); 399 } 400 401 /** 402 * Wraps an output stream for encoding byte data using the {@link Base64} 403 * encoding scheme. 404 * 405 * <p> It is recommended to promptly close the returned output stream after 406 * use, during which it will flush all possible leftover bytes to the underlying 407 * output stream. Closing the returned output stream will close the underlying 408 * output stream. 409 * 410 * @param os 411 * the output stream. 412 * @return the output stream for encoding the byte data into the 413 * specified Base64 encoded format 414 */ 415 public OutputStream wrap(OutputStream os) { 416 Objects.requireNonNull(os); 417 return new EncOutputStream(os, isURL ? toBase64URL : toBase64, 418 newline, linemax); 419 } 420 421 private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) { 422 char[] base64 = isURL? toBase64URL : toBase64; 423 byte[] sa = src.array(); 424 int sp = src.arrayOffset() + src.position(); 425 int sl = src.arrayOffset() + src.limit(); 426 byte[] da = dst.array(); 427 int dp = dst.arrayOffset() + dst.position(); 428 int dl = dst.arrayOffset() + dst.limit(); 429 int dp00 = dp; 430 int dpos = 0; // dp of each line 431 if (linemax > 0 && bytesOut > 0) 432 dpos = bytesOut % (linemax + newline.length); 433 try { 434 if (dpos == linemax && sp < src.limit()) { 435 if (dp + newline.length > dl) 436 return dp - dp00 + bytesOut; 437 for (byte b : newline){ 438 dst.put(dp++, b); 439 } 440 dpos = 0; 441 } 442 sl = sp + (sl - sp) / 3 * 3; 443 while (sp < sl) { 444 int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3 445 : sl - sp; 446 int sl0 = Math.min(sp + slen, sl); 447 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 448 if (dp0 + 4 > dl) { 449 sp = sp0; dp = dp0; 450 return dp0 - dp00 + bytesOut; 451 } 452 int bits = (sa[sp0++] & 0xff) << 16 | 453 (sa[sp0++] & 0xff) << 8 | 454 (sa[sp0++] & 0xff); 455 da[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 456 da[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 457 da[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 458 da[dp0++] = (byte)base64[bits & 0x3f]; 459 } 460 int n = (sl0 - sp) / 3 * 4; 461 dpos += n; 462 dp += n; 463 sp = sl0; 464 if (dpos == linemax && sp < src.limit()) { 465 if (dp + newline.length > dl) 466 return dp - dp00 + bytesOut; 467 for (byte b : newline){ 468 da[dp++] = b; 469 } 470 dpos = 0; 471 } 472 } 473 sl = src.arrayOffset() + src.limit(); 474 if (sp < sl && dl >= dp + 4) { // 1 or 2 leftover bytes 475 int b0 = sa[sp++] & 0xff; 476 da[dp++] = (byte)base64[b0 >> 2]; 477 if (sp == sl) { 478 da[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 479 da[dp++] = '='; 480 da[dp++] = '='; 481 } else { 482 int b1 = sa[sp++] & 0xff; 483 da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 484 da[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 485 da[dp++] = '='; 486 } 487 } 488 return dp - dp00 + bytesOut; 489 } finally { 490 src.position(sp - src.arrayOffset()); 491 dst.position(dp - dst.arrayOffset()); 492 } 493 } 494 495 private int encodeBuffer(ByteBuffer src, ByteBuffer dst, int bytesOut) { 496 char[] base64 = isURL? toBase64URL : toBase64; 497 int sp = src.position(); 498 int sl = src.limit(); 499 int dp = dst.position(); 500 int dl = dst.limit(); 501 int dp00 = dp; 502 503 int dpos = 0; // dp of each line 504 if (linemax > 0 && bytesOut > 0) 505 dpos = bytesOut % (linemax + newline.length); 506 try { 507 if (dpos == linemax && sp < src.limit()) { 508 if (dp + newline.length > dl) 509 return dp - dp00 + bytesOut; 510 for (byte b : newline){ 511 dst.put(dp++, b); 512 } 513 dpos = 0; 514 } 515 sl = sp + (sl - sp) / 3 * 3; 516 while (sp < sl) { 517 int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3 518 : sl - sp; 519 int sl0 = Math.min(sp + slen, sl); 520 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 521 if (dp0 + 4 > dl) { 522 sp = sp0; dp = dp0; 523 return dp0 - dp00 + bytesOut; 524 } 525 int bits = (src.get(sp0++) & 0xff) << 16 | 526 (src.get(sp0++) & 0xff) << 8 | 527 (src.get(sp0++) & 0xff); 528 dst.put(dp0++, (byte)base64[(bits >>> 18) & 0x3f]); 529 dst.put(dp0++, (byte)base64[(bits >>> 12) & 0x3f]); 530 dst.put(dp0++, (byte)base64[(bits >>> 6) & 0x3f]); 531 dst.put(dp0++, (byte)base64[bits & 0x3f]); 532 } 533 int n = (sl0 - sp) / 3 * 4; 534 dpos += n; 535 dp += n; 536 sp = sl0; 537 if (dpos == linemax && sp < src.limit()) { 538 if (dp + newline.length > dl) 539 return dp - dp00 + bytesOut; 540 for (byte b : newline){ 541 dst.put(dp++, b); 542 } 543 dpos = 0; 544 } 545 } 546 if (sp < src.limit() && dl >= dp + 4) { // 1 or 2 leftover bytes 547 int b0 = src.get(sp++) & 0xff; 548 dst.put(dp++, (byte)base64[b0 >> 2]); 549 if (sp == src.limit()) { 550 dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]); 551 dst.put(dp++, (byte)'='); 552 dst.put(dp++, (byte)'='); 553 } else { 554 int b1 = src.get(sp++) & 0xff; 555 dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 556 dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]); 557 dst.put(dp++, (byte)'='); 558 } 559 } 560 return dp - dp00 + bytesOut; 561 } finally { 562 src.position(sp); 563 dst.position(dp); 564 } 565 } 566 567 private int encode0(byte[] src, int off, int end, byte[] dst) { 568 char[] base64 = isURL ? toBase64URL : toBase64; 569 int sp = off; 570 int slen = (end - off) / 3 * 3; 571 int sl = off + slen; 572 if (linemax > 0 && slen > linemax / 4 * 3) 573 slen = linemax / 4 * 3; 574 int dp = 0; 575 while (sp < sl) { 576 int sl0 = Math.min(sp + slen, sl); 577 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 578 int bits = (src[sp0++] & 0xff) << 16 | 579 (src[sp0++] & 0xff) << 8 | 580 (src[sp0++] & 0xff); 581 dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 582 dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 583 dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 584 dst[dp0++] = (byte)base64[bits & 0x3f]; 585 } 586 int dlen = (sl0 - sp) / 3 * 4; 587 dp += dlen; 588 sp = sl0; 589 if (dlen == linemax && sp < end) { 590 for (byte b : newline){ 591 dst[dp++] = b; 592 } 593 } 594 } 595 if (sp < end) { // 1 or 2 leftover bytes 596 int b0 = src[sp++] & 0xff; 597 dst[dp++] = (byte)base64[b0 >> 2]; 598 if (sp == end) { 599 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 600 dst[dp++] = '='; 601 dst[dp++] = '='; 602 } else { 603 int b1 = src[sp++] & 0xff; 604 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 605 dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 606 dst[dp++] = '='; 607 } 608 } 609 return dp; 610 } 611 } 612 613 /** 614 * This class implements a decoder for decoding byte data using the 615 * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 616 * 617 * <p> Instances of {@link Decoder} class are safe for use by 618 * multiple concurrent threads. 619 * 620 * <p> Unless otherwise noted, passing a {@code null} argument to 621 * a method of this class will cause a 622 * {@link java.lang.NullPointerException NullPointerException} to 623 * be thrown. 624 * 625 * @see Encoder 626 * @since 1.8 627 */ 628 public static class Decoder { 629 630 private final boolean isURL; 631 private final boolean isMIME; 632 633 private Decoder(boolean isURL, boolean isMIME) { 634 this.isURL = isURL; 635 this.isMIME = isMIME; 636 } 637 638 /** 639 * Lookup table for decoding unicode characters drawn from the 640 * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 641 * their 6-bit positive integer equivalents. Characters that 642 * are not in the Base64 alphabet but fall within the bounds of 643 * the array are encoded to -1. 644 * 645 */ 646 private static final int[] fromBase64 = new int[256]; 647 static { 648 Arrays.fill(fromBase64, -1); 649 for (int i = 0; i < Encoder.toBase64.length; i++) 650 fromBase64[Encoder.toBase64[i]] = i; 651 fromBase64['='] = -2; 652 } 653 654 /** 655 * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 656 * as specified in Table2 of the RFC 4648. 657 */ 658 private static final int[] fromBase64URL = new int[256]; 659 660 static { 661 Arrays.fill(fromBase64URL, -1); 662 for (int i = 0; i < Encoder.toBase64URL.length; i++) 663 fromBase64URL[Encoder.toBase64URL[i]] = i; 664 fromBase64URL['='] = -2; 665 } 666 667 static final Decoder RFC4648 = new Decoder(false, false); 668 static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 669 static final Decoder RFC2045 = new Decoder(false, true); 670 671 /** 672 * Decodes all bytes from the input byte array using the {@link Base64} 673 * encoding scheme, writing the results into a newly-allocated output 674 * byte array. The returned byte array is of the length of the resulting 675 * bytes. 676 * 677 * @param src 678 * the byte array to decode 679 * 680 * @return A newly-allocated byte array containing the decoded bytes. 681 * 682 * @throws IllegalArgumentException 683 * if {@code src} is not in valid Base64 scheme 684 */ 685 public byte[] decode(byte[] src) { 686 byte[] dst = new byte[outLength(src, 0, src.length)]; 687 int ret = decode0(src, 0, src.length, dst); 688 if (ret != dst.length) { 689 dst = Arrays.copyOf(dst, ret); 690 } 691 return dst; 692 } 693 694 /** 695 * Decodes a Base64 encoded String into a newly-allocated byte array 696 * using the {@link Base64} encoding scheme. 697 * 698 * <p> An invocation of this method has exactly the same effect as invoking 699 * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} 700 * 701 * @param src 702 * the string to decode 703 * 704 * @return A newly-allocated byte array containing the decoded bytes. 705 * 706 * @throws IllegalArgumentException 707 * if {@code src} is not in valid Base64 scheme 708 */ 709 public byte[] decode(String src) { 710 return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 711 } 712 713 /** 714 * Decodes all bytes from the input byte array using the {@link Base64} 715 * encoding scheme, writing the results into the given output byte array, 716 * starting at offset 0. 717 * 718 * <p> It is the responsibility of the invoker of this method to make 719 * sure the output byte array {@code dst} has enough space for decoding 720 * all bytes from the input byte array. No bytes will be be written to 721 * the output byte array if the output byte array is not big enough. 722 * 723 * <p> If the input byte array is not in valid Base64 encoding scheme 724 * then some bytes may have been written to the output byte array before 725 * IllegalargumentException is thrown. 726 * 727 * @param src 728 * the byte array to decode 729 * @param dst 730 * the output byte array 731 * 732 * @return The number of bytes written to the output byte array 733 * 734 * @throws IllegalArgumentException 735 * if {@code src} is not in valid Base64 scheme, or {@code dst} 736 * does not have enough space for decoding all input bytes. 737 */ 738 public int decode(byte[] src, byte[] dst) { 739 int len = outLength(src, 0, src.length); 740 if (dst.length < len) 741 throw new IllegalArgumentException( 742 "Output byte array is too small for decoding all input bytes"); 743 return decode0(src, 0, src.length, dst); 744 } 745 746 /** 747 * Decodes all bytes from the input byte buffer using the {@link Base64} 748 * encoding scheme, writing the results into a newly-allocated ByteBuffer. 749 * 750 * <p> Upon return, the source buffer's position will be updated to 751 * its limit; its limit will not have been changed. The returned 752 * output buffer's position will be zero and its limit will be the 753 * number of resulting decoded bytes 754 * 755 * @param buffer 756 * the ByteBuffer to decode 757 * 758 * @return A newly-allocated byte buffer containing the decoded bytes 759 * 760 * @throws IllegalArgumentException 761 * if {@code src} is not in valid Base64 scheme. 762 */ 763 public ByteBuffer decode(ByteBuffer buffer) { 764 int pos0 = buffer.position(); 765 try { 766 byte[] src; 767 int sp, sl; 768 if (buffer.hasArray()) { 769 src = buffer.array(); 770 sp = buffer.arrayOffset() + buffer.position(); 771 sl = buffer.arrayOffset() + buffer.limit(); 772 buffer.position(buffer.limit()); 773 } else { 774 src = new byte[buffer.remaining()]; 775 buffer.get(src); 776 sp = 0; 777 sl = src.length; 778 } 779 byte[] dst = new byte[outLength(src, sp, sl)]; 780 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 781 } catch (IllegalArgumentException iae) { 782 buffer.position(pos0); 783 throw iae; 784 } 785 } 786 787 /** 788 * Decodes as many bytes as possible from the input byte buffer 789 * using the {@link Base64} encoding scheme, writing the resulting 790 * bytes to the given output byte buffer. 791 * 792 * <p>The buffers are read from, and written to, starting at their 793 * current positions. Upon return, the input and output buffers' 794 * positions will be advanced to reflect the bytes read and written, 795 * but their limits will not be modified. 796 * 797 * <p> If the input buffer is not in valid Base64 encoding scheme 798 * then some bytes may have been written to the output buffer 799 * before IllegalArgumentException is thrown. The positions of 800 * both input and output buffer will not be advanced in this case. 801 * 802 * <p>The decoding operation will end and return if all remaining 803 * bytes in the input buffer have been decoded and written to the 804 * output buffer. 805 * 806 * <p> The decoding operation will stop and return if the output 807 * buffer has insufficient space to decode any more input bytes. 808 * The decoding operation can be continued, if there is more bytes 809 * in input buffer to be decoded, by invoking this method again with 810 * an output buffer that has more {@linkplain java.nio.Buffer#remaining 811 * remaining} bytes. This is typically done by draining any decoded 812 * bytes from the output buffer. 813 * 814 * <p><b>Recommended Usage Example</b> 815 * <pre> 816 * ByteBuffer src = ...; 817 * ByteBuffer dst = ...; 818 * Base64.Decoder dec = Base64.getDecoder(); 819 * 820 * while (src.hasRemaining()) { 821 * 822 * // prepare the output byte buffer 823 * dst.clear(); 824 * dec.decode(src, dst); 825 * 826 * // read bytes from the output buffer 827 * dst.flip(); 828 * ... 829 * } 830 * </pre> 831 * 832 * @param src 833 * the input byte buffer to decode 834 * @param dst 835 * the output byte buffer 836 * 837 * @return The number of bytes written to the output byte buffer during 838 * this decoding invocation 839 * 840 * @throws IllegalArgumentException 841 * if {@code src} is not in valid Base64 scheme. 842 */ 843 public int decode(ByteBuffer src, ByteBuffer dst) { 844 int sp0 = src.position(); 845 int dp0 = dst.position(); 846 try { 847 if (src.hasArray() && dst.hasArray()) 848 return decodeArray(src, dst); 849 return decodeBuffer(src, dst); 850 } catch (IllegalArgumentException iae) { 851 src.position(sp0); 852 dst.position(dp0); 853 throw iae; 854 } 855 } 856 857 /** 858 * Returns an input stream for decoding {@link Base64} encoded byte stream. 859 * 860 * <p> Closing the returned input stream will close the underlying 861 * input stream. 862 * 863 * @param is 864 * the input stream 865 * 866 * @return the input stream for decoding the specified Base64 encoded 867 * byte stream 868 */ 869 public InputStream wrap(InputStream is) { 870 Objects.requireNonNull(is); 871 return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 872 } 873 874 private int decodeArray(ByteBuffer src, ByteBuffer dst) { 875 int[] base64 = isURL ? fromBase64URL : fromBase64; 876 int bits = 0; 877 int shiftto = 18; // pos of first byte of 4-byte atom 878 byte[] sa = src.array(); 879 int sp = src.arrayOffset() + src.position(); 880 int sl = src.arrayOffset() + src.limit(); 881 byte[] da = dst.array(); 882 int dp = dst.arrayOffset() + dst.position(); 883 int dl = dst.arrayOffset() + dst.limit(); 884 int dp0 = dp; 885 int mark = sp; 886 boolean padding = false; 887 try { 888 while (sp < sl) { 889 int b = sa[sp++] & 0xff; 890 if ((b = base64[b]) < 0) { 891 if (b == -2) { // padding byte 892 padding = true; 893 break; 894 } 895 if (isMIME) // skip if for rfc2045 896 continue; 897 else 898 throw new IllegalArgumentException( 899 "Illegal base64 character " + 900 Integer.toString(sa[sp - 1], 16)); 901 } 902 bits |= (b << shiftto); 903 shiftto -= 6; 904 if (shiftto < 0) { 905 if (dl < dp + 3) 906 return dp - dp0; 907 da[dp++] = (byte)(bits >> 16); 908 da[dp++] = (byte)(bits >> 8); 909 da[dp++] = (byte)(bits); 910 shiftto = 18; 911 bits = 0; 912 mark = sp; 913 } 914 } 915 if (shiftto == 6) { 916 if (dl - dp < 1) 917 return dp - dp0; 918 if (padding && (sp + 1 != sl || sa[sp++] != '=')) 919 throw new IllegalArgumentException( 920 "Input buffer has wrong 4-byte ending unit"); 921 da[dp++] = (byte)(bits >> 16); 922 mark = sp; 923 } else if (shiftto == 0) { 924 if (dl - dp < 2) 925 return dp - dp0; 926 if (padding && sp != sl) 927 throw new IllegalArgumentException( 928 "Input buffer has wrong 4-byte ending unit"); 929 da[dp++] = (byte)(bits >> 16); 930 da[dp++] = (byte)(bits >> 8); 931 mark = sp; 932 } else if (padding || shiftto != 18) { 933 throw new IllegalArgumentException( 934 "Last unit does not have enough valid bits"); 935 } 936 return dp - dp0; 937 } finally { 938 src.position(mark); 939 dst.position(dp); 940 } 941 } 942 943 private int decodeBuffer(ByteBuffer src, ByteBuffer dst) { 944 int[] base64 = isURL ? fromBase64URL : fromBase64; 945 int bits = 0; 946 int shiftto = 18; // pos of first byte of 4-byte atom 947 int sp = src.position(); 948 int sl = src.limit(); 949 int dp = dst.position(); 950 int dl = dst.limit(); 951 int dp0 = dp; 952 int mark = sp; 953 boolean padding = false; 954 955 try { 956 while (sp < sl) { 957 int b = src.get(sp++) & 0xff; 958 if ((b = base64[b]) < 0) { 959 if (b == -2) { // padding byte 960 padding = true; 961 break; 962 } 963 if (isMIME) // skip if for rfc2045 964 continue; 965 else 966 throw new IllegalArgumentException( 967 "Illegal base64 character " + 968 Integer.toString(src.get(sp - 1), 16)); 969 } 970 bits |= (b << shiftto); 971 shiftto -= 6; 972 if (shiftto < 0) { 973 if (dl < dp + 3) 974 return dp - dp0; 975 dst.put(dp++, (byte)(bits >> 16)); 976 dst.put(dp++, (byte)(bits >> 8)); 977 dst.put(dp++, (byte)(bits)); 978 shiftto = 18; 979 bits = 0; 980 mark = sp; 981 } 982 } 983 if (shiftto == 6) { 984 if (dl - dp < 1) 985 return dp - dp0; 986 if (padding && (sp + 1 != sl || src.get(sp++) != '=')) 987 throw new IllegalArgumentException( 988 "Input buffer has wrong 4-byte ending unit"); 989 dst.put(dp++, (byte)(bits >> 16)); 990 mark = sp; 991 } else if (shiftto == 0) { 992 if (dl - dp < 2) 993 return dp - dp0; 994 if (padding && sp != sl) 995 throw new IllegalArgumentException( 996 "Input buffer has wrong 4-byte ending unit"); 997 dst.put(dp++, (byte)(bits >> 16)); 998 dst.put(dp++, (byte)(bits >> 8)); 999 mark = sp; 1000 } else if (padding || shiftto != 18) { 1001 throw new IllegalArgumentException( 1002 "Last unit does not have enough valid bits"); 1003 } 1004 return dp - dp0; 1005 } finally { 1006 src.position(mark); 1007 dst.position(dp); 1008 } 1009 } 1010 1011 private int outLength(byte[] src, int sp, int sl) { 1012 int[] base64 = isURL ? fromBase64URL : fromBase64; 1013 int paddings = 0; 1014 int len = sl - sp; 1015 if (len == 0) 1016 return 0; 1017 if (len < 2) { 1018 if (isMIME && base64[0] == -1) 1019 return 0; 1020 throw new IllegalArgumentException( 1021 "Input byte[] should at least have 2 bytes for base64 bytes"); 1022 } 1023 if (src[sl - 1] == '=') { 1024 paddings++; 1025 if (src[sl - 2] == '=') 1026 paddings++; 1027 } 1028 if (isMIME) { 1029 // scan all bytes to fill out all non-alphabet. a performance 1030 // trade-off of pre-scan or Arrays.copyOf 1031 int n = 0; 1032 while (sp < sl) { 1033 int b = src[sp++] & 0xff; 1034 if (b == '=') 1035 break; 1036 if ((b = base64[b]) == -1) 1037 n++; 1038 } 1039 len -= n; 1040 } 1041 if (paddings == 0 && (len & 0x3) != 0) 1042 paddings = 4 - (len & 0x3); 1043 return 3 * ((len + 3) / 4) - paddings; 1044 } 1045 1046 private int decode0(byte[] src, int sp, int sl, byte[] dst) { 1047 int[] base64 = isURL ? fromBase64URL : fromBase64; 1048 int dp = 0; 1049 int bits = 0; 1050 int shiftto = 18; // pos of first byte of 4-byte atom 1051 boolean padding = false; 1052 while (sp < sl) { 1053 int b = src[sp++] & 0xff; 1054 if ((b = base64[b]) < 0) { 1055 if (b == -2) { // padding byte 1056 padding = true; 1057 break; 1058 } 1059 if (isMIME) // skip if for rfc2045 1060 continue; 1061 else 1062 throw new IllegalArgumentException( 1063 "Illegal base64 character " + 1064 Integer.toString(src[sp - 1], 16)); 1065 } 1066 bits |= (b << shiftto); 1067 shiftto -= 6; 1068 if (shiftto < 0) { 1069 dst[dp++] = (byte)(bits >> 16); 1070 dst[dp++] = (byte)(bits >> 8); 1071 dst[dp++] = (byte)(bits); 1072 shiftto = 18; 1073 bits = 0; 1074 } 1075 } 1076 // reach end of byte arry or hit padding '=' characters. 1077 // if '=' presents, they must be the last one or two. 1078 if (shiftto == 6) { // xx== 1079 if (padding && (sp + 1 != sl || src[sp] != '=')) 1080 throw new IllegalArgumentException( 1081 "Input byte array has wrong 4-byte ending unit"); 1082 dst[dp++] = (byte)(bits >> 16); 1083 } else if (shiftto == 0) { // xxx= 1084 if (padding && sp != sl) 1085 throw new IllegalArgumentException( 1086 "Input byte array has wrong 4-byte ending unit"); 1087 dst[dp++] = (byte)(bits >> 16); 1088 dst[dp++] = (byte)(bits >> 8); 1089 } else if (padding || shiftto != 18) { 1090 throw new IllegalArgumentException( 1091 "last unit does not have enough bytes"); 1092 } 1093 return dp; 1094 } 1095 } 1096 1097 /* 1098 * An output stream for encoding bytes into the Base64. 1099 */ 1100 private static class EncOutputStream extends FilterOutputStream { 1101 1102 private int leftover = 0; 1103 private int b0, b1, b2; 1104 private boolean closed = false; 1105 1106 private final char[] base64; // byte->base64 mapping 1107 private final byte[] newline; // line separator, if needed 1108 private final int linemax; 1109 private int linepos = 0; 1110 1111 EncOutputStream(OutputStream os, 1112 char[] base64, byte[] newline, int linemax) { 1113 super(os); 1114 this.base64 = base64; 1115 this.newline = newline; 1116 this.linemax = linemax; 1117 } 1118 1119 @Override 1120 public void write(int b) throws IOException { 1121 byte[] buf = new byte[1]; 1122 buf[0] = (byte)(b & 0xff); 1123 write(buf, 0, 1); 1124 } 1125 1126 private void checkNewline() throws IOException { 1127 if (linepos == linemax) { 1128 out.write(newline); 1129 linepos = 0; 1130 } 1131 } 1132 1133 @Override 1134 public void write(byte[] b, int off, int len) throws IOException { 1135 if (closed) 1136 throw new IOException("Stream is closed"); 1137 if (off < 0 || len < 0 || off + len > b.length) 1138 throw new ArrayIndexOutOfBoundsException(); 1139 if (len == 0) 1140 return; 1141 if (leftover != 0) { 1142 if (leftover == 1) { 1143 b1 = b[off++] & 0xff; 1144 len--; 1145 if (len == 0) { 1146 leftover++; 1147 return; 1148 } 1149 } 1150 b2 = b[off++] & 0xff; 1151 len--; 1152 checkNewline(); 1153 out.write(base64[b0 >> 2]); 1154 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1155 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 1156 out.write(base64[b2 & 0x3f]); 1157 linepos += 4; 1158 } 1159 int nBits24 = len / 3; 1160 leftover = len - (nBits24 * 3); 1161 while (nBits24-- > 0) { 1162 checkNewline(); 1163 int bits = (b[off++] & 0xff) << 16 | 1164 (b[off++] & 0xff) << 8 | 1165 (b[off++] & 0xff); 1166 out.write(base64[(bits >>> 18) & 0x3f]); 1167 out.write(base64[(bits >>> 12) & 0x3f]); 1168 out.write(base64[(bits >>> 6) & 0x3f]); 1169 out.write(base64[bits & 0x3f]); 1170 linepos += 4; 1171 } 1172 if (leftover == 1) { 1173 b0 = b[off++] & 0xff; 1174 } else if (leftover == 2) { 1175 b0 = b[off++] & 0xff; 1176 b1 = b[off++] & 0xff; 1177 } 1178 } 1179 1180 @Override 1181 public void close() throws IOException { 1182 if (!closed) { 1183 closed = true; 1184 if (leftover == 1) { 1185 checkNewline(); 1186 out.write(base64[b0 >> 2]); 1187 out.write(base64[(b0 << 4) & 0x3f]); 1188 out.write('='); 1189 out.write('='); 1190 } else if (leftover == 2) { 1191 checkNewline(); 1192 out.write(base64[b0 >> 2]); 1193 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1194 out.write(base64[(b1 << 2) & 0x3f]); 1195 out.write('='); 1196 } 1197 leftover = 0; 1198 out.close(); 1199 } 1200 } 1201 } 1202 1203 /* 1204 * An input stream for decoding Base64 bytes 1205 */ 1206 private static class DecInputStream extends InputStream { 1207 1208 private final InputStream is; 1209 private final boolean isMIME; 1210 private final int[] base64; // base64 -> byte mapping 1211 private int bits = 0; // 24-bit buffer for decoding 1212 private int nextin = 18; // next available "off" in "bits" for input; 1213 // -> 18, 12, 6, 0 1214 private int nextout = -8; // next available "off" in "bits" for output; 1215 // -> 8, 0, -8 (no byte for output) 1216 private boolean eof = false; 1217 private boolean closed = false; 1218 1219 DecInputStream(InputStream is, int[] base64, boolean isMIME) { 1220 this.is = is; 1221 this.base64 = base64; 1222 this.isMIME = isMIME; 1223 } 1224 1225 private byte[] sbBuf = new byte[1]; 1226 1227 @Override 1228 public int read() throws IOException { 1229 return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 1230 } 1231 1232 @Override 1233 public int read(byte[] b, int off, int len) throws IOException { 1234 if (closed) 1235 throw new IOException("Stream is closed"); 1236 if (eof && nextout < 0) // eof and no leftover 1237 return -1; 1238 if (off < 0 || len < 0 || len > b.length - off) 1239 throw new IndexOutOfBoundsException(); 1240 int oldOff = off; 1241 if (nextout >= 0) { // leftover output byte(s) in bits buf 1242 do { 1243 if (len == 0) 1244 return off - oldOff; 1245 b[off++] = (byte)(bits >> nextout); 1246 len--; 1247 nextout -= 8; 1248 } while (nextout >= 0); 1249 bits = 0; 1250 } 1251 while (len > 0) { 1252 int v = is.read(); 1253 if (v == -1) { 1254 eof = true; 1255 if (nextin != 18) 1256 throw new IOException("Base64 stream has un-decoded dangling byte(s)."); 1257 if (off == oldOff) 1258 return -1; 1259 else 1260 return off - oldOff; 1261 } 1262 if (v == '=') { // padding byte(s) 1263 if (nextin != 6 && nextin != 0) { 1264 throw new IOException("Illegal base64 ending sequence:" + nextin); 1265 } 1266 b[off++] = (byte)(bits >> (16)); 1267 len--; 1268 if (nextin == 0) { // only one padding byte 1269 if (len == 0) { // no enough output space 1270 bits >>= 8; // shift to lowest byte 1271 nextout = 0; 1272 } else { 1273 b[off++] = (byte) (bits >> 8); 1274 } 1275 } 1276 eof = true; 1277 break; 1278 } 1279 if ((v = base64[v]) == -1) { 1280 if (isMIME) // skip if for rfc2045 1281 continue; 1282 else 1283 throw new IOException("Illegal base64 character " + 1284 Integer.toString(v, 16)); 1285 } 1286 bits |= (v << nextin); 1287 if (nextin == 0) { 1288 nextin = 18; // clear for next 1289 nextout = 16; 1290 while (nextout >= 0) { 1291 b[off++] = (byte)(bits >> nextout); 1292 len--; 1293 nextout -= 8; 1294 if (len == 0 && nextout >= 0) { // don't clean "bits" 1295 return off - oldOff; 1296 } 1297 } 1298 bits = 0; 1299 } else { 1300 nextin -= 6; 1301 } 1302 } 1303 return off - oldOff; 1304 } 1305 1306 @Override 1307 public int available() throws IOException { 1308 if (closed) 1309 throw new IOException("Stream is closed"); 1310 return is.available(); // TBD: 1311 } 1312 1313 @Override 1314 public void close() throws IOException { 1315 if (!closed) { 1316 closed = true; 1317 is.close(); 1318 } 1319 } 1320 } 1321 }