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> If the input byte stream is not in valid Base64 encoding scheme 861 * then IllegalArgumentException will be thrown when reading any invalid 862 * Base64 byte(s) from the returned input stream. 863 * 864 * <p> Closing the returned input stream will close the underlying 865 * input stream. 866 * 867 * @param is 868 * the input stream 869 * 870 * @return the input stream for decoding the specified Base64 encoded 871 * byte stream 872 */ 873 public InputStream wrap(InputStream is) { 874 Objects.requireNonNull(is); 875 return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 876 } 877 878 private int decodeArray(ByteBuffer src, ByteBuffer dst) { 879 int[] base64 = isURL ? fromBase64URL : fromBase64; 880 int bits = 0; 881 int shiftto = 18; // pos of first byte of 4-byte atom 882 byte[] sa = src.array(); 883 int sp = src.arrayOffset() + src.position(); 884 int sl = src.arrayOffset() + src.limit(); 885 byte[] da = dst.array(); 886 int dp = dst.arrayOffset() + dst.position(); 887 int dl = dst.arrayOffset() + dst.limit(); 888 int dp0 = dp; 889 int mark = sp; 890 boolean padding = false; 891 try { 892 while (sp < sl) { 893 int b = sa[sp++] & 0xff; 894 if ((b = base64[b]) < 0) { 895 if (b == -2) { // padding byte 896 padding = true; 897 break; 898 } 899 if (isMIME) // skip if for rfc2045 900 continue; 901 else 902 throw new IllegalArgumentException( 903 "Illegal base64 character " + 904 Integer.toString(sa[sp - 1], 16)); 905 } 906 bits |= (b << shiftto); 907 shiftto -= 6; 908 if (shiftto < 0) { 909 if (dl < dp + 3) 910 return dp - dp0; 911 da[dp++] = (byte)(bits >> 16); 912 da[dp++] = (byte)(bits >> 8); 913 da[dp++] = (byte)(bits); 914 shiftto = 18; 915 bits = 0; 916 mark = sp; 917 } 918 } 919 if (shiftto == 6) { 920 if (dl - dp < 1) 921 return dp - dp0; 922 if (padding && (sp + 1 != sl || sa[sp++] != '=')) 923 throw new IllegalArgumentException( 924 "Input buffer has wrong 4-byte ending unit"); 925 da[dp++] = (byte)(bits >> 16); 926 mark = sp; 927 } else if (shiftto == 0) { 928 if (dl - dp < 2) 929 return dp - dp0; 930 if (padding && sp != sl) 931 throw new IllegalArgumentException( 932 "Input buffer has wrong 4-byte ending unit"); 933 da[dp++] = (byte)(bits >> 16); 934 da[dp++] = (byte)(bits >> 8); 935 mark = sp; 936 } else if (padding || shiftto != 18) { 937 throw new IllegalArgumentException( 938 "Last unit does not have enough valid bits"); 939 } 940 return dp - dp0; 941 } finally { 942 src.position(mark); 943 dst.position(dp); 944 } 945 } 946 947 private int decodeBuffer(ByteBuffer src, ByteBuffer dst) { 948 int[] base64 = isURL ? fromBase64URL : fromBase64; 949 int bits = 0; 950 int shiftto = 18; // pos of first byte of 4-byte atom 951 int sp = src.position(); 952 int sl = src.limit(); 953 int dp = dst.position(); 954 int dl = dst.limit(); 955 int dp0 = dp; 956 int mark = sp; 957 boolean padding = false; 958 959 try { 960 while (sp < sl) { 961 int b = src.get(sp++) & 0xff; 962 if ((b = base64[b]) < 0) { 963 if (b == -2) { // padding byte 964 padding = true; 965 break; 966 } 967 if (isMIME) // skip if for rfc2045 968 continue; 969 else 970 throw new IllegalArgumentException( 971 "Illegal base64 character " + 972 Integer.toString(src.get(sp - 1), 16)); 973 } 974 bits |= (b << shiftto); 975 shiftto -= 6; 976 if (shiftto < 0) { 977 if (dl < dp + 3) 978 return dp - dp0; 979 dst.put(dp++, (byte)(bits >> 16)); 980 dst.put(dp++, (byte)(bits >> 8)); 981 dst.put(dp++, (byte)(bits)); 982 shiftto = 18; 983 bits = 0; 984 mark = sp; 985 } 986 } 987 if (shiftto == 6) { 988 if (dl - dp < 1) 989 return dp - dp0; 990 if (padding && (sp + 1 != sl || src.get(sp++) != '=')) 991 throw new IllegalArgumentException( 992 "Input buffer has wrong 4-byte ending unit"); 993 dst.put(dp++, (byte)(bits >> 16)); 994 mark = sp; 995 } else if (shiftto == 0) { 996 if (dl - dp < 2) 997 return dp - dp0; 998 if (padding && sp != sl) 999 throw new IllegalArgumentException( 1000 "Input buffer has wrong 4-byte ending unit"); 1001 dst.put(dp++, (byte)(bits >> 16)); 1002 dst.put(dp++, (byte)(bits >> 8)); 1003 mark = sp; 1004 } else if (padding || shiftto != 18) { 1005 throw new IllegalArgumentException( 1006 "Last unit does not have enough valid bits"); 1007 } 1008 return dp - dp0; 1009 } finally { 1010 src.position(mark); 1011 dst.position(dp); 1012 } 1013 } 1014 1015 private int outLength(byte[] src, int sp, int sl) { 1016 int[] base64 = isURL ? fromBase64URL : fromBase64; 1017 int paddings = 0; 1018 int len = sl - sp; 1019 if (len == 0) 1020 return 0; 1021 if (len < 2) { 1022 if (isMIME && base64[0] == -1) 1023 return 0; 1024 throw new IllegalArgumentException( 1025 "Input byte[] should at least have 2 bytes for base64 bytes"); 1026 } 1027 if (src[sl - 1] == '=') { 1028 paddings++; 1029 if (src[sl - 2] == '=') 1030 paddings++; 1031 } 1032 if (isMIME) { 1033 // scan all bytes to fill out all non-alphabet. a performance 1034 // trade-off of pre-scan or Arrays.copyOf 1035 int n = 0; 1036 while (sp < sl) { 1037 int b = src[sp++] & 0xff; 1038 if (b == '=') 1039 break; 1040 if ((b = base64[b]) == -1) 1041 n++; 1042 } 1043 len -= n; 1044 } 1045 if (paddings == 0 && (len & 0x3) != 0) 1046 paddings = 4 - (len & 0x3); 1047 return 3 * ((len + 3) / 4) - paddings; 1048 } 1049 1050 private int decode0(byte[] src, int sp, int sl, byte[] dst) { 1051 int[] base64 = isURL ? fromBase64URL : fromBase64; 1052 int dp = 0; 1053 int bits = 0; 1054 int shiftto = 18; // pos of first byte of 4-byte atom 1055 boolean padding = false; 1056 while (sp < sl) { 1057 int b = src[sp++] & 0xff; 1058 if ((b = base64[b]) < 0) { 1059 if (b == -2) { // padding byte 1060 padding = true; 1061 break; 1062 } 1063 if (isMIME) // skip if for rfc2045 1064 continue; 1065 else 1066 throw new IllegalArgumentException( 1067 "Illegal base64 character " + 1068 Integer.toString(src[sp - 1], 16)); 1069 } 1070 bits |= (b << shiftto); 1071 shiftto -= 6; 1072 if (shiftto < 0) { 1073 dst[dp++] = (byte)(bits >> 16); 1074 dst[dp++] = (byte)(bits >> 8); 1075 dst[dp++] = (byte)(bits); 1076 shiftto = 18; 1077 bits = 0; 1078 } 1079 } 1080 // reach end of byte arry or hit padding '=' characters. 1081 // if '=' presents, they must be the last one or two. 1082 if (shiftto == 6) { // xx== 1083 if (padding && (sp + 1 != sl || src[sp] != '=')) 1084 throw new IllegalArgumentException( 1085 "Input byte array has wrong 4-byte ending unit"); 1086 dst[dp++] = (byte)(bits >> 16); 1087 } else if (shiftto == 0) { // xxx= 1088 if (padding && sp != sl) 1089 throw new IllegalArgumentException( 1090 "Input byte array has wrong 4-byte ending unit"); 1091 dst[dp++] = (byte)(bits >> 16); 1092 dst[dp++] = (byte)(bits >> 8); 1093 } else if (padding || shiftto != 18) { 1094 throw new IllegalArgumentException( 1095 "last unit does not have enough bytes"); 1096 } 1097 return dp; 1098 } 1099 } 1100 1101 /* 1102 * An output stream for encoding bytes into the Base64. 1103 */ 1104 private static class EncOutputStream extends FilterOutputStream { 1105 1106 private int leftover = 0; 1107 private int b0, b1, b2; 1108 private boolean closed = false; 1109 1110 private final char[] base64; // byte->base64 mapping 1111 private final byte[] newline; // line separator, if needed 1112 private final int linemax; 1113 private int linepos = 0; 1114 1115 EncOutputStream(OutputStream os, 1116 char[] base64, byte[] newline, int linemax) { 1117 super(os); 1118 this.base64 = base64; 1119 this.newline = newline; 1120 this.linemax = linemax; 1121 } 1122 1123 @Override 1124 public void write(int b) throws IOException { 1125 byte[] buf = new byte[1]; 1126 buf[0] = (byte)(b & 0xff); 1127 write(buf, 0, 1); 1128 } 1129 1130 private void checkNewline() throws IOException { 1131 if (linepos == linemax) { 1132 out.write(newline); 1133 linepos = 0; 1134 } 1135 } 1136 1137 @Override 1138 public void write(byte[] b, int off, int len) throws IOException { 1139 if (closed) 1140 throw new IOException("Stream is closed"); 1141 if (off < 0 || len < 0 || off + len > b.length) 1142 throw new ArrayIndexOutOfBoundsException(); 1143 if (len == 0) 1144 return; 1145 if (leftover != 0) { 1146 if (leftover == 1) { 1147 b1 = b[off++] & 0xff; 1148 len--; 1149 if (len == 0) { 1150 leftover++; 1151 return; 1152 } 1153 } 1154 b2 = b[off++] & 0xff; 1155 len--; 1156 checkNewline(); 1157 out.write(base64[b0 >> 2]); 1158 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1159 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 1160 out.write(base64[b2 & 0x3f]); 1161 linepos += 4; 1162 } 1163 int nBits24 = len / 3; 1164 leftover = len - (nBits24 * 3); 1165 while (nBits24-- > 0) { 1166 checkNewline(); 1167 int bits = (b[off++] & 0xff) << 16 | 1168 (b[off++] & 0xff) << 8 | 1169 (b[off++] & 0xff); 1170 out.write(base64[(bits >>> 18) & 0x3f]); 1171 out.write(base64[(bits >>> 12) & 0x3f]); 1172 out.write(base64[(bits >>> 6) & 0x3f]); 1173 out.write(base64[bits & 0x3f]); 1174 linepos += 4; 1175 } 1176 if (leftover == 1) { 1177 b0 = b[off++] & 0xff; 1178 } else if (leftover == 2) { 1179 b0 = b[off++] & 0xff; 1180 b1 = b[off++] & 0xff; 1181 } 1182 } 1183 1184 @Override 1185 public void close() throws IOException { 1186 if (!closed) { 1187 closed = true; 1188 if (leftover == 1) { 1189 checkNewline(); 1190 out.write(base64[b0 >> 2]); 1191 out.write(base64[(b0 << 4) & 0x3f]); 1192 out.write('='); 1193 out.write('='); 1194 } else if (leftover == 2) { 1195 checkNewline(); 1196 out.write(base64[b0 >> 2]); 1197 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1198 out.write(base64[(b1 << 2) & 0x3f]); 1199 out.write('='); 1200 } 1201 leftover = 0; 1202 out.close(); 1203 } 1204 } 1205 } 1206 1207 /* 1208 * An input stream for decoding Base64 bytes 1209 */ 1210 private static class DecInputStream extends InputStream { 1211 1212 private final InputStream is; 1213 private final boolean isMIME; 1214 private final int[] base64; // base64 -> byte mapping 1215 private int bits = 0; // 24-bit buffer for decoding 1216 private int nextin = 18; // next available "off" in "bits" for input; 1217 // -> 18, 12, 6, 0 1218 private int nextout = -8; // next available "off" in "bits" for output; 1219 // -> 8, 0, -8 (no byte for output) 1220 private boolean eof = false; 1221 private boolean closed = false; 1222 1223 DecInputStream(InputStream is, int[] base64, boolean isMIME) { 1224 this.is = is; 1225 this.base64 = base64; 1226 this.isMIME = isMIME; 1227 } 1228 1229 private byte[] sbBuf = new byte[1]; 1230 1231 @Override 1232 public int read() throws IOException { 1233 return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 1234 } 1235 1236 @Override 1237 public int read(byte[] b, int off, int len) throws IOException { 1238 if (closed) 1239 throw new IOException("Stream is closed"); 1240 if (eof && nextout < 0) // eof and no leftover 1241 return -1; 1242 if (off < 0 || len < 0 || len > b.length - off) 1243 throw new IndexOutOfBoundsException(); 1244 int oldOff = off; 1245 if (nextout >= 0) { // leftover output byte(s) in bits buf 1246 do { 1247 if (len == 0) 1248 return off - oldOff; 1249 b[off++] = (byte)(bits >> nextout); 1250 len--; 1251 nextout -= 8; 1252 } while (nextout >= 0); 1253 bits = 0; 1254 } 1255 while (len > 0) { 1256 int v = is.read(); 1257 if (v == -1) { 1258 eof = true; 1259 if (nextin != 18) 1260 throw new IllegalArgumentException( 1261 "Base64 stream has un-decoded dangling byte(s)."); 1262 if (off == oldOff) 1263 return -1; 1264 else 1265 return off - oldOff; 1266 } 1267 if (v == '=') { // padding byte(s) 1268 if (nextin != 6 && nextin != 0) { 1269 throw new IllegalArgumentException( 1270 "Illegal base64 ending sequence:" + nextin); 1271 } 1272 b[off++] = (byte)(bits >> (16)); 1273 len--; 1274 if (nextin == 0) { // only one padding byte 1275 if (len == 0) { // no enough output space 1276 bits >>= 8; // shift to lowest byte 1277 nextout = 0; 1278 } else { 1279 b[off++] = (byte) (bits >> 8); 1280 } 1281 } 1282 eof = true; 1283 break; 1284 } 1285 if ((v = base64[v]) == -1) { 1286 if (isMIME) // skip if for rfc2045 1287 continue; 1288 else 1289 throw new IllegalArgumentException( 1290 "Illegal base64 character " + Integer.toString(v, 16)); 1291 } 1292 bits |= (v << nextin); 1293 if (nextin == 0) { 1294 nextin = 18; // clear for next 1295 nextout = 16; 1296 while (nextout >= 0) { 1297 b[off++] = (byte)(bits >> nextout); 1298 len--; 1299 nextout -= 8; 1300 if (len == 0 && nextout >= 0) { // don't clean "bits" 1301 return off - oldOff; 1302 } 1303 } 1304 bits = 0; 1305 } else { 1306 nextin -= 6; 1307 } 1308 } 1309 return off - oldOff; 1310 } 1311 1312 @Override 1313 public int available() throws IOException { 1314 if (closed) 1315 throw new IOException("Stream is closed"); 1316 return is.available(); // TBD: 1317 } 1318 1319 @Override 1320 public void close() throws IOException { 1321 if (!closed) { 1322 closed = true; 1323 is.close(); 1324 } 1325 } 1326 } 1327 }