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 ISO-8859-1} 293 * 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 Buffer#remaining remaining} bytes. This is typically 362 * done by draining any encoded bytes from the output buffer. The 363 * 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 return new EncOutputStream(os, isURL ? toBase64URL : toBase64, 417 newline, linemax); 418 } 419 420 private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) { 421 char[] base64 = isURL? toBase64URL : toBase64; 422 byte[] sa = src.array(); 423 int sp = src.arrayOffset() + src.position(); 424 int sl = src.arrayOffset() + src.limit(); 425 byte[] da = dst.array(); 426 int dp = dst.arrayOffset() + dst.position(); 427 int dl = dst.arrayOffset() + dst.limit(); 428 int dp00 = dp; 429 int dpos = 0; // dp of each line 430 if (linemax > 0 && bytesOut > 0) 431 dpos = bytesOut % (linemax + newline.length); 432 try { 433 if (dpos == linemax && sp < src.limit()) { 434 if (dp + newline.length > dl) 435 return dp - dp00 + bytesOut; 436 for (byte b : newline){ 437 dst.put(dp++, b); 438 } 439 dpos = 0; 440 } 441 sl = sp + (sl - sp) / 3 * 3; 442 while (sp < sl) { 443 int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3 444 : sl - sp; 445 int sl0 = Math.min(sp + slen, sl); 446 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 447 if (dp0 + 4 > dl) { 448 sp = sp0; dp = dp0; 449 return dp0 - dp00 + bytesOut; 450 } 451 int bits = (sa[sp0++] & 0xff) << 16 | 452 (sa[sp0++] & 0xff) << 8 | 453 (sa[sp0++] & 0xff); 454 da[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 455 da[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 456 da[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 457 da[dp0++] = (byte)base64[bits & 0x3f]; 458 } 459 int n = (sl0 - sp) / 3 * 4; 460 dpos += n; 461 dp += n; 462 sp = sl0; 463 if (dpos == linemax && sp < src.limit()) { 464 if (dp + newline.length > dl) 465 return dp - dp00 + bytesOut; 466 for (byte b : newline){ 467 da[dp++] = b; 468 } 469 dpos = 0; 470 } 471 } 472 sl = src.arrayOffset() + src.limit(); 473 if (sp < sl && dl >= dp + 4) { // 1 or 2 leftover bytes 474 int b0 = sa[sp++] & 0xff; 475 da[dp++] = (byte)base64[b0 >> 2]; 476 if (sp == sl) { 477 da[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 478 da[dp++] = '='; 479 da[dp++] = '='; 480 } else { 481 int b1 = sa[sp++] & 0xff; 482 da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 483 da[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 484 da[dp++] = '='; 485 } 486 } 487 return dp - dp00 + bytesOut; 488 } finally { 489 src.position(sp - src.arrayOffset()); 490 dst.position(dp - dst.arrayOffset()); 491 } 492 } 493 494 private int encodeBuffer(ByteBuffer src, ByteBuffer dst, int bytesOut) { 495 char[] base64 = isURL? toBase64URL : toBase64; 496 int sp = src.position(); 497 int sl = src.limit(); 498 int dp = dst.position(); 499 int dl = dst.limit(); 500 int dp00 = dp; 501 502 int dpos = 0; // dp of each line 503 if (linemax > 0 && bytesOut > 0) 504 dpos = bytesOut % (linemax + newline.length); 505 try { 506 if (dpos == linemax && sp < src.limit()) { 507 if (dp + newline.length > dl) 508 return dp - dp00 + bytesOut; 509 for (byte b : newline){ 510 dst.put(dp++, b); 511 } 512 dpos = 0; 513 } 514 sl = sp + (sl - sp) / 3 * 3; 515 while (sp < sl) { 516 int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3 517 : sl - sp; 518 int sl0 = Math.min(sp + slen, sl); 519 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 520 if (dp0 + 4 > dl) { 521 sp = sp0; dp = dp0; 522 return dp0 - dp00 + bytesOut; 523 } 524 int bits = (src.get(sp0++) & 0xff) << 16 | 525 (src.get(sp0++) & 0xff) << 8 | 526 (src.get(sp0++) & 0xff); 527 dst.put(dp0++, (byte)base64[(bits >>> 18) & 0x3f]); 528 dst.put(dp0++, (byte)base64[(bits >>> 12) & 0x3f]); 529 dst.put(dp0++, (byte)base64[(bits >>> 6) & 0x3f]); 530 dst.put(dp0++, (byte)base64[bits & 0x3f]); 531 } 532 int n = (sl0 - sp) / 3 * 4; 533 dpos += n; 534 dp += n; 535 sp = sl0; 536 if (dpos == linemax && sp < src.limit()) { 537 if (dp + newline.length > dl) 538 return dp - dp00 + bytesOut; 539 for (byte b : newline){ 540 dst.put(dp++, b); 541 } 542 dpos = 0; 543 } 544 } 545 if (sp < src.limit() && dl >= dp + 4) { // 1 or 2 leftover bytes 546 int b0 = src.get(sp++) & 0xff; 547 dst.put(dp++, (byte)base64[b0 >> 2]); 548 if (sp == src.limit()) { 549 dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]); 550 dst.put(dp++, (byte)'='); 551 dst.put(dp++, (byte)'='); 552 } else { 553 int b1 = src.get(sp++) & 0xff; 554 dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 555 dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]); 556 dst.put(dp++, (byte)'='); 557 } 558 } 559 return dp - dp00 + bytesOut; 560 } finally { 561 src.position(sp); 562 dst.position(dp); 563 } 564 } 565 566 private int encode0(byte[] src, int off, int end, byte[] dst) { 567 char[] base64 = isURL ? toBase64URL : toBase64; 568 int sp = off; 569 int slen = (end - off) / 3 * 3; 570 int sl = off + slen; 571 if (linemax > 0 && slen > linemax / 4 * 3) 572 slen = linemax / 4 * 3; 573 int dp = 0; 574 while (sp < sl) { 575 int sl0 = Math.min(sp + slen, sl); 576 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 577 int bits = (src[sp0++] & 0xff) << 16 | 578 (src[sp0++] & 0xff) << 8 | 579 (src[sp0++] & 0xff); 580 dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 581 dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 582 dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 583 dst[dp0++] = (byte)base64[bits & 0x3f]; 584 } 585 int dlen = (sl0 - sp) / 3 * 4; 586 dp += dlen; 587 sp = sl0; 588 if (dlen == linemax && sp < end) { 589 for (byte b : newline){ 590 dst[dp++] = b; 591 } 592 } 593 } 594 if (sp < end) { // 1 or 2 leftover bytes 595 int b0 = src[sp++] & 0xff; 596 dst[dp++] = (byte)base64[b0 >> 2]; 597 if (sp == end) { 598 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 599 dst[dp++] = '='; 600 dst[dp++] = '='; 601 } else { 602 int b1 = src[sp++] & 0xff; 603 dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 604 dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 605 dst[dp++] = '='; 606 } 607 } 608 return dp; 609 } 610 } 611 612 /** 613 * This class implements a decoder for decoding byte data using the 614 * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 615 * 616 * <p> Instances of {@link Decoder} class are safe for use by 617 * multiple concurrent threads. 618 * 619 * <p> Unless otherwise noted, passing a {@code null} argument to 620 * a method of this class will cause a 621 * {@link java.lang.NullPointerException NullPointerException} to 622 * be thrown. 623 * 624 * @see Encoder 625 * @since 1.8 626 */ 627 public static class Decoder { 628 629 private final boolean isURL; 630 private final boolean isMIME; 631 632 private Decoder(boolean isURL, boolean isMIME) { 633 this.isURL = isURL; 634 this.isMIME = isMIME; 635 } 636 637 /** 638 * Lookup table for decoding unicode characters drawn from the 639 * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 640 * their 6-bit positive integer equivalents. Characters that 641 * are not in the Base64 alphabet but fall within the bounds of 642 * the array are encoded to -1. 643 * 644 */ 645 private static final int[] fromBase64 = new int[256]; 646 static { 647 Arrays.fill(fromBase64, -1); 648 for (int i = 0; i < Encoder.toBase64.length; i++) 649 fromBase64[Encoder.toBase64[i]] = i; 650 fromBase64['='] = -2; 651 } 652 653 /** 654 * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 655 * as specified in Table2 of the RFC 4648. 656 */ 657 private static final int[] fromBase64URL = new int[256]; 658 659 static { 660 Arrays.fill(fromBase64URL, -1); 661 for (int i = 0; i < Encoder.toBase64URL.length; i++) 662 fromBase64URL[Encoder.toBase64URL[i]] = i; 663 fromBase64URL['='] = -2; 664 } 665 666 static final Decoder RFC4648 = new Decoder(false, false); 667 static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 668 static final Decoder RFC2045 = new Decoder(false, true); 669 670 /** 671 * Decodes all bytes from the input byte array using the {@link Base64} 672 * encoding scheme, writing the results into a newly-allocated output 673 * byte array. The returned byte array is of the length of the resulting 674 * bytes. 675 * 676 * @param src 677 * the byte array to decode 678 * 679 * @return A newly-allocated byte array containing the decoded bytes. 680 * 681 * @throws IllegalArgumentException 682 * if {@code src} is not in valid Base64 scheme 683 */ 684 public byte[] decode(byte[] src) { 685 byte[] dst = new byte[outLength(src, 0, src.length)]; 686 int ret = decode0(src, 0, src.length, dst); 687 if (ret != dst.length) { 688 dst = Arrays.copyOf(dst, ret); 689 } 690 return dst; 691 } 692 693 /** 694 * Decodes a Base64 encoded String into a newly-allocated byte array 695 * using the {@link Base64} encoding scheme. 696 * 697 * <p> An invocation of this method has exactly the same effect as invoking 698 * {@code return decode(src.getBytes(StandardCharsets.ISO_8859_1))} 699 * 700 * @param src 701 * the string to decode 702 * 703 * @return A newly-allocated byte array containing the decoded bytes. 704 * 705 * @throws IllegalArgumentException 706 * if {@code src} is not in valid Base64 scheme 707 */ 708 public byte[] decode(String src) { 709 return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 710 } 711 712 /** 713 * Decodes all bytes from the input byte array using the {@link Base64} 714 * encoding scheme, writing the results into the given output byte array, 715 * starting at offset 0. 716 * 717 * <p> It is the responsibility of the invoker of this method to make 718 * sure the output byte array {@code dst} has enough space for decoding 719 * all bytes from the input byte array. No bytes will be be written to 720 * the output byte array if the output byte array is not big enough. 721 * 722 * <p> If the input byte array is not in valid Base64 encoding scheme 723 * then some bytes may have been written to the output byte array before 724 * IllegalargumentException is thrown. 725 * 726 * @param src 727 * the byte array to decode 728 * @param dst 729 * the output byte array 730 * 731 * @return The number of bytes written to the output byte array 732 * 733 * @throws IllegalArgumentException 734 * if {@code src} is not in valid Base64 scheme, or {@code dst} 735 * does not have enough space for decoding all input bytes. 736 */ 737 public int decode(byte[] src, byte[] dst) { 738 int len = outLength(src, 0, src.length); 739 if (dst.length < len) 740 throw new IllegalArgumentException( 741 "Output byte array is too small for decoding all input bytes"); 742 return decode0(src, 0, src.length, dst); 743 } 744 745 /** 746 * Decodes all bytes from the input byte buffer using the {@link Base64} 747 * encoding scheme, writing the results into a newly-allocated ByteBuffer. 748 * 749 * <p> Upon return, the source buffer's position will be updated to 750 * its limit; its limit will not have been changed. The returned 751 * output buffer's position will be zero and its limit will be the 752 * number of resulting decoded bytes 753 * 754 * @param buffer 755 * the ByteBuffer to decode 756 * 757 * @return A newly-allocated byte buffer containing the decoded bytes 758 * 759 * @throws IllegalArgumentException 760 * if {@code src} is not in valid Base64 scheme. 761 */ 762 public ByteBuffer decode(ByteBuffer buffer) { 763 int pos0 = buffer.position(); 764 try { 765 byte[] src; 766 int sp, sl; 767 if (buffer.hasArray()) { 768 src = buffer.array(); 769 sp = buffer.arrayOffset() + buffer.position(); 770 sl = buffer.arrayOffset() + buffer.limit(); 771 buffer.position(buffer.limit()); 772 } else { 773 src = new byte[buffer.remaining()]; 774 buffer.get(src); 775 sp = 0; 776 sl = src.length; 777 } 778 byte[] dst = new byte[outLength(src, sp, sl)]; 779 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 780 } catch (IllegalArgumentException iae) { 781 buffer.position(pos0); 782 throw iae; 783 } 784 } 785 786 /** 787 * Decodes as many bytes as possible from the input byte buffer 788 * using the {@link Base64} encoding scheme, writing the resulting 789 * bytes to the given output byte buffer. 790 * 791 * <p>The buffers are read from, and written to, starting at their 792 * current positions. Upon return, the input and output buffers' 793 * positions will be advanced to reflect the bytes read and written, 794 * but their limits will not be modified. 795 * 796 * <p> If the input buffer is not in valid Base64 encoding scheme 797 * then some bytes may have been written to the output buffer 798 * before IllegalArgumentException is thrown. The positions of 799 * both input and output buffer will not be advanced in this case. 800 * 801 * <p>The decoding operation will end and return if all remaining 802 * bytes in the input buffer have been decoded and written to the 803 * output buffer. 804 * 805 * <p> The decoding operation will stop and return if the output 806 * buffer has insufficient space to decode any more input bytes. 807 * The decoding operation can be continued, if there is more bytes 808 * in input buffer to be decoded, by invoking this method again with 809 * an output buffer that has more {@linkplain Buffer#remaining remaining} 810 * bytes.This is typically done by draining any decoded bytes from the 811 * output buffer. 812 * 813 * <p><b>Recommended Usage Example</b> 814 * <pre> 815 * ByteBuffer src = ...; 816 * ByteBuffer dst = ...; 817 * Base64.Decoder dec = Base64.getDecoder(); 818 * 819 * while (src.hasRemaining()) { 820 * 821 * // prepare the output byte buffer 822 * dst.clear(); 823 * dec.decode(src, dst); 824 * 825 * // read bytes from the output buffer 826 * dst.flip(); 827 * ... 828 * } 829 * </pre> 830 * 831 * @param src 832 * the input byte buffer to decode 833 * @param dst 834 * the output byte buffer 835 * 836 * @return The number of bytes written to the output byte buffer during 837 * this decoding invocation 838 * 839 * @throws IllegalArgumentException 840 * if {@code src} is not in valid Base64 scheme. 841 */ 842 public int decode(ByteBuffer src, ByteBuffer dst) { 843 int sp0 = src.position(); 844 int dp0 = dst.position(); 845 try { 846 if (src.hasArray() && dst.hasArray()) 847 return decodeArray(src, dst); 848 return decodeBuffer(src, dst); 849 } catch (IllegalArgumentException iae) { 850 src.position(sp0); 851 dst.position(dp0); 852 throw iae; 853 } 854 } 855 856 /** 857 * Returns an input stream for decoding {@link Base64} encoded byte stream. 858 * 859 * <p> Closing the returned input stream will close the underlying 860 * input stream. 861 * 862 * @param is 863 * the input stream 864 * 865 * @return the input stream for decoding the specified Base64 encoded 866 * byte stream 867 */ 868 public InputStream wrap(InputStream is) { 869 return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 870 } 871 872 private int decodeArray(ByteBuffer src, ByteBuffer dst) { 873 int[] base64 = isURL ? fromBase64URL : fromBase64; 874 int bits = 0; 875 int shiftto = 18; // pos of first byte of 4-byte atom 876 byte[] sa = src.array(); 877 int sp = src.arrayOffset() + src.position(); 878 int sl = src.arrayOffset() + src.limit(); 879 byte[] da = dst.array(); 880 int dp = dst.arrayOffset() + dst.position(); 881 int dl = dst.arrayOffset() + dst.limit(); 882 int dp0 = dp; 883 int mark = sp; 884 boolean padding = false; 885 try { 886 while (sp < sl) { 887 int b = sa[sp++] & 0xff; 888 if ((b = base64[b]) < 0) { 889 if (b == -2) { // padding byte 890 padding = true; 891 break; 892 } 893 if (isMIME) // skip if for rfc2045 894 continue; 895 else 896 throw new IllegalArgumentException( 897 "Illegal base64 character " + 898 Integer.toString(sa[sp - 1], 16)); 899 } 900 bits |= (b << shiftto); 901 shiftto -= 6; 902 if (shiftto < 0) { 903 if (dl < dp + 3) 904 return dp; 905 da[dp++] = (byte)(bits >> 16); 906 da[dp++] = (byte)(bits >> 8); 907 da[dp++] = (byte)(bits); 908 shiftto = 18; 909 bits = 0; 910 mark = sp; 911 } 912 } 913 if (shiftto == 6) { 914 if (dl - dp < 1) 915 return dp; 916 if (padding && (sp + 1 != sl || sa[sp++] != '=')) 917 throw new IllegalArgumentException( 918 "Input buffer has wrong 4-byte ending unit"); 919 da[dp++] = (byte)(bits >> 16); 920 mark = sp; 921 } else if (shiftto == 0) { 922 if (dl - dp < 2) 923 return dp; 924 if (padding && sp != sl) 925 throw new IllegalArgumentException( 926 "Input buffer has wrong 4-byte ending unit"); 927 da[dp++] = (byte)(bits >> 16); 928 da[dp++] = (byte)(bits >> 8); 929 mark = sp; 930 } else if (padding || shiftto != 18) { 931 throw new IllegalArgumentException( 932 "Last unit does not have enough valid bits"); 933 } 934 return dp - dp0; 935 } finally { 936 src.position(mark); 937 dst.position(dp); 938 } 939 } 940 941 private int decodeBuffer(ByteBuffer src, ByteBuffer dst) { 942 int[] base64 = isURL ? fromBase64URL : fromBase64; 943 int bits = 0; 944 int shiftto = 18; // pos of first byte of 4-byte atom 945 int sp = src.position(); 946 int sl = src.limit(); 947 int dp = dst.position(); 948 int dl = dst.limit(); 949 int dp0 = dp; 950 int mark = sp; 951 boolean padding = false; 952 953 try { 954 while (sp < sl) { 955 int b = src.get(sp++) & 0xff; 956 if ((b = base64[b]) < 0) { 957 if (b == -2) { // padding byte 958 padding = true; 959 break; 960 } 961 if (isMIME) // skip if for rfc2045 962 continue; 963 else 964 throw new IllegalArgumentException( 965 "Illegal base64 character " + 966 Integer.toString(src.get(sp - 1), 16)); 967 } 968 bits |= (b << shiftto); 969 shiftto -= 6; 970 if (shiftto < 0) { 971 if (dl < dp + 3) 972 return dp; 973 dst.put(dp++, (byte)(bits >> 16)); 974 dst.put(dp++, (byte)(bits >> 8)); 975 dst.put(dp++, (byte)(bits)); 976 shiftto = 18; 977 bits = 0; 978 mark = sp; 979 } 980 } 981 if (shiftto == 6) { 982 if (dl - dp < 1) 983 return dp; 984 if (padding && (sp + 1 != sl || src.get(sp++) != '=')) 985 throw new IllegalArgumentException( 986 "Input buffer has wrong 4-byte ending unit"); 987 dst.put(dp++, (byte)(bits >> 16)); 988 mark = sp; 989 } else if (shiftto == 0) { 990 if (dl - dp < 2) 991 return dp; 992 if (padding && sp != sl) 993 throw new IllegalArgumentException( 994 "Input buffer has wrong 4-byte ending unit"); 995 dst.put(dp++, (byte)(bits >> 16)); 996 dst.put(dp++, (byte)(bits >> 8)); 997 mark = sp; 998 } else if (padding || shiftto != 18) { 999 throw new IllegalArgumentException( 1000 "Last unit does not have enough valid bits"); 1001 } 1002 return dp - dp0; 1003 } finally { 1004 src.position(mark); 1005 dst.position(dp); 1006 } 1007 } 1008 1009 private int outLength(byte[] src, int sp, int sl) { 1010 int[] base64 = isURL ? fromBase64URL : fromBase64; 1011 int paddings = 0; 1012 int len = sl - sp; 1013 if (len == 0) 1014 return 0; 1015 if (len < 2) 1016 throw new IllegalArgumentException( 1017 "Input byte[] should at least have 2 bytes for base64 bytes"); 1018 if (src[sl - 1] == '=') { 1019 paddings++; 1020 if (src[sl - 2] == '=') 1021 paddings++; 1022 } 1023 if (isMIME) { 1024 // scan all bytes to fill out all non-alphabet. a performance 1025 // trade-off of pre-scan or Arrays.copyOf 1026 int n = 0; 1027 while (sp < sl) { 1028 int b = src[sp++] & 0xff; 1029 if (b == '=') 1030 break; 1031 if ((b = base64[b]) == -1) 1032 n++; 1033 } 1034 len -= n; 1035 } 1036 if (paddings == 0 && (len & 0x3) != 0) 1037 paddings = 4 - (len & 0x3); 1038 return 3 * ((len + 3) / 4) - paddings; 1039 } 1040 1041 private int decode0(byte[] src, int sp, int sl, byte[] dst) { 1042 int[] base64 = isURL ? fromBase64URL : fromBase64; 1043 int dp = 0; 1044 int bits = 0; 1045 int shiftto = 18; // pos of first byte of 4-byte atom 1046 boolean padding = false; 1047 while (sp < sl) { 1048 int b = src[sp++] & 0xff; 1049 if ((b = base64[b]) < 0) { 1050 if (b == -2) { // padding byte 1051 padding = true; 1052 break; 1053 } 1054 if (isMIME) // skip if for rfc2045 1055 continue; 1056 else 1057 throw new IllegalArgumentException( 1058 "Illegal base64 character " + 1059 Integer.toString(src[sp - 1], 16)); 1060 } 1061 bits |= (b << shiftto); 1062 shiftto -= 6; 1063 if (shiftto < 0) { 1064 dst[dp++] = (byte)(bits >> 16); 1065 dst[dp++] = (byte)(bits >> 8); 1066 dst[dp++] = (byte)(bits); 1067 shiftto = 18; 1068 bits = 0; 1069 } 1070 } 1071 // reach end of byte arry or hit padding '=' characters. 1072 // if '=' presents, they must be the last one or two. 1073 if (shiftto == 6) { // xx== 1074 if (padding && (sp + 1 != sl || src[sp] != '=')) 1075 throw new IllegalArgumentException( 1076 "Input byte array has wrong 4-byte ending unit"); 1077 dst[dp++] = (byte)(bits >> 16); 1078 } else if (shiftto == 0) { // xxx= 1079 if (padding && sp != sl) 1080 throw new IllegalArgumentException( 1081 "Input byte array has wrong 4-byte ending unit"); 1082 dst[dp++] = (byte)(bits >> 16); 1083 dst[dp++] = (byte)(bits >> 8); 1084 } else if (padding || shiftto != 18) { 1085 throw new IllegalArgumentException( 1086 "last unit does not have enough bytes"); 1087 } 1088 return dp; 1089 } 1090 } 1091 1092 /* 1093 * An output stream for encoding bytes into the Base64. 1094 */ 1095 private static class EncOutputStream extends FilterOutputStream { 1096 1097 private int leftover = 0; 1098 private int b0, b1, b2; 1099 private boolean closed = false; 1100 1101 private final char[] base64; // byte->base64 mapping 1102 private final byte[] newline; // line separator, if needed 1103 private final int linemax; 1104 private int linepos = 0; 1105 1106 EncOutputStream(OutputStream os, 1107 char[] base64, byte[] newline, int linemax) { 1108 super(os); 1109 this.base64 = base64; 1110 this.newline = newline; 1111 this.linemax = linemax; 1112 } 1113 1114 @Override 1115 public void write(int b) throws IOException { 1116 byte[] buf = new byte[1]; 1117 buf[0] = (byte)(b & 0xff); 1118 write(buf, 0, 1); 1119 } 1120 1121 private void checkNewline() throws IOException { 1122 if (linepos == linemax) { 1123 out.write(newline); 1124 linepos = 0; 1125 } 1126 } 1127 1128 @Override 1129 public void write(byte[] b, int off, int len) throws IOException { 1130 if (closed) 1131 throw new IOException("Stream is closed"); 1132 if (off < 0 || len < 0 || off + len > b.length) 1133 throw new ArrayIndexOutOfBoundsException(); 1134 if (len == 0) 1135 return; 1136 if (leftover != 0) { 1137 if (leftover == 1) { 1138 b1 = b[off++] & 0xff; 1139 len--; 1140 if (len == 0) { 1141 leftover++; 1142 return; 1143 } 1144 } 1145 b2 = b[off++] & 0xff; 1146 len--; 1147 checkNewline(); 1148 out.write(base64[b0 >> 2]); 1149 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1150 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 1151 out.write(base64[b2 & 0x3f]); 1152 linepos += 4; 1153 } 1154 int nBits24 = len / 3; 1155 leftover = len - (nBits24 * 3); 1156 while (nBits24-- > 0) { 1157 checkNewline(); 1158 int bits = (b[off++] & 0xff) << 16 | 1159 (b[off++] & 0xff) << 8 | 1160 (b[off++] & 0xff); 1161 out.write(base64[(bits >>> 18) & 0x3f]); 1162 out.write(base64[(bits >>> 12) & 0x3f]); 1163 out.write(base64[(bits >>> 6) & 0x3f]); 1164 out.write(base64[bits & 0x3f]); 1165 linepos += 4; 1166 } 1167 if (leftover == 1) { 1168 b0 = b[off++] & 0xff; 1169 } else if (leftover == 2) { 1170 b0 = b[off++] & 0xff; 1171 b1 = b[off++] & 0xff; 1172 } 1173 } 1174 1175 @Override 1176 public void close() throws IOException { 1177 if (!closed) { 1178 closed = true; 1179 if (leftover == 1) { 1180 checkNewline(); 1181 out.write(base64[b0 >> 2]); 1182 out.write(base64[(b0 << 4) & 0x3f]); 1183 out.write('='); 1184 out.write('='); 1185 } else if (leftover == 2) { 1186 checkNewline(); 1187 out.write(base64[b0 >> 2]); 1188 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1189 out.write(base64[(b1 << 2) & 0x3f]); 1190 out.write('='); 1191 } 1192 leftover = 0; 1193 out.close(); 1194 } 1195 } 1196 } 1197 1198 /* 1199 * An input stream for decoding Base64 bytes 1200 */ 1201 private static class DecInputStream extends InputStream { 1202 1203 private final InputStream is; 1204 private final boolean isMIME; 1205 private final int[] base64; // base64 -> byte mapping 1206 private int bits = 0; // 24-bit buffer for decoding 1207 private int nextin = 18; // next available "off" in "bits" for input; 1208 // -> 18, 12, 6, 0 1209 private int nextout = -8; // next available "off" in "bits" for output; 1210 // -> 8, 0, -8 (no byte for output) 1211 private boolean eof = false; 1212 private boolean closed = false; 1213 1214 DecInputStream(InputStream is, int[] base64, boolean isMIME) { 1215 this.is = is; 1216 this.base64 = base64; 1217 this.isMIME = isMIME; 1218 } 1219 1220 private byte[] sbBuf = new byte[1]; 1221 1222 @Override 1223 public int read() throws IOException { 1224 return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 1225 } 1226 1227 @Override 1228 public int read(byte[] b, int off, int len) throws IOException { 1229 if (closed) 1230 throw new IOException("Stream is closed"); 1231 if (eof && nextout < 0) // eof and no leftover 1232 return -1; 1233 if (off < 0 || len < 0 || len > b.length - off) 1234 throw new IndexOutOfBoundsException(); 1235 int oldOff = off; 1236 if (nextout >= 0) { // leftover output byte(s) in bits buf 1237 do { 1238 if (len == 0) 1239 return off - oldOff; 1240 b[off++] = (byte)(bits >> nextout); 1241 len--; 1242 nextout -= 8; 1243 } while (nextout >= 0); 1244 bits = 0; 1245 } 1246 while (len > 0) { 1247 int v = is.read(); 1248 if (v == -1) { 1249 eof = true; 1250 if (nextin != 18) 1251 throw new IOException("Base64 stream has un-decoded dangling byte(s)."); 1252 if (off == oldOff) 1253 return -1; 1254 else 1255 return off - oldOff; 1256 } 1257 if (v == '=') { // padding byte(s) 1258 if (nextin != 6 && nextin != 0) { 1259 throw new IOException("Illegal base64 ending sequence:" + nextin); 1260 } 1261 b[off++] = (byte)(bits >> (16)); 1262 len--; 1263 if (nextin == 0) { // only one padding byte 1264 if (len == 0) { // no enough output space 1265 bits >>= 8; // shift to lowest byte 1266 nextout = 0; 1267 } else { 1268 b[off++] = (byte) (bits >> 8); 1269 } 1270 } 1271 eof = true; 1272 break; 1273 } 1274 if ((v = base64[v]) == -1) { 1275 if (isMIME) // skip if for rfc2045 1276 continue; 1277 else 1278 throw new IOException("Illegal base64 character " + 1279 Integer.toString(v, 16)); 1280 } 1281 bits |= (v << nextin); 1282 if (nextin == 0) { 1283 nextin = 18; // clear for next 1284 nextout = 16; 1285 while (nextout >= 0) { 1286 b[off++] = (byte)(bits >> nextout); 1287 len--; 1288 nextout -= 8; 1289 if (len == 0 && nextout >= 0) { // don't clean "bits" 1290 return off - oldOff; 1291 } 1292 } 1293 bits = 0; 1294 } else { 1295 nextin -= 6; 1296 } 1297 } 1298 return off - oldOff; 1299 } 1300 1301 @Override 1302 public int available() throws IOException { 1303 if (closed) 1304 throw new IOException("Stream is closed"); 1305 return is.available(); // TBD: 1306 } 1307 1308 @Override 1309 public void close() throws IOException { 1310 if (!closed) { 1311 closed = true; 1312 is.close(); 1313 } 1314 } 1315 } 1316 }