1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util; 27 28 import java.io.FilterOutputStream; 29 import java.io.InputStream; 30 import java.io.IOException; 31 import java.io.OutputStream; 32 import java.nio.ByteBuffer; 33 import java.nio.charset.StandardCharsets; 34 35 /** 36 * This class consists exclusively of static methods for obtaining 37 * encoders and decoders for the Base64 encoding scheme. The 38 * implementation of this class supports the following types of Base64 39 * as specified in 40 * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and 41 * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. 42 * 43 * <ul> 44 * <li><a name="basic"><b>Basic</b></a> 45 * <p> Uses "The Base64 Alphabet" as specified in Table 1 of 46 * RFC 4648 and RFC 2045 for encoding and decoding operation. 47 * The encoder does not add any line feed (line separator) 48 * character. The decoder rejects data that contains characters 49 * outside the base64 alphabet.</p></li> 50 * 51 * <li><a name="url"><b>URL and Filename safe</b></a> 52 * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified 53 * in Table 2 of RFC 4648 for encoding and decoding. The 54 * encoder does not add any line feed (line separator) character. 55 * The decoder rejects data that contains characters outside the 56 * base64 alphabet.</p></li> 57 * 58 * <li><a name="mime"><b>MIME</b></a> 59 * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of 60 * RFC 2045 for encoding and decoding operation. The encoded output 61 * must be represented in lines of no more than 76 characters each 62 * and uses a carriage return {@code '\r'} followed immediately by 63 * a linefeed {@code '\n'} as the line separator. No line separator 64 * is added to the end of the encoded output. All line separators 65 * or other characters not found in the base64 alphabet table are 66 * ignored in decoding operation.</p></li> 67 * </ul> 68 * 69 * <p> Unless otherwise noted, passing a {@code null} argument to a 70 * method of this class will cause a {@link java.lang.NullPointerException 71 * NullPointerException} to be thrown. 72 * 73 * @author Xueming Shen 74 * @since 1.8 75 */ 76 77 public class Base64 { 78 79 private Base64() {} 80 81 /** 82 * Returns a {@link Encoder} that encodes using the 83 * <a href="#basic">Basic</a> type base64 encoding scheme. 84 * 85 * @return A Base64 encoder. 86 */ 87 public static Encoder getEncoder() { 88 return Encoder.RFC4648; 89 } 90 91 /** 92 * Returns a {@link Encoder} that encodes using the 93 * <a href="#url">URL and Filename safe</a> type base64 94 * encoding scheme. 95 * 96 * @return A Base64 encoder. 97 */ 98 public static Encoder getUrlEncoder() { 99 return Encoder.RFC4648_URLSAFE; 100 } 101 102 /** 103 * Returns a {@link Encoder} that encodes using the 104 * <a href="#mime">MIME</a> type base64 encoding scheme. 105 * 106 * @return A Base64 encoder. 107 */ 108 public static Encoder getMimeEncoder() { 109 return Encoder.RFC2045; 110 } 111 112 /** 113 * Returns a {@link Encoder} that encodes using the 114 * <a href="#mime">MIME</a> type base64 encoding scheme 115 * with specified line length and line separators. 116 * 117 * @param lineLength 118 * the length of each output line (rounded down to nearest multiple 119 * of 4). If {@code lineLength <= 0} the output will not be separated 120 * in lines 121 * @param lineSeparator 122 * the line separator for each output line 123 * 124 * @return A Base64 encoder. 125 * 126 * @throws IllegalArgumentException if {@code lineSeparator} includes any 127 * character of "The Base64 Alphabet" as specified in Table 1 of 128 * RFC 2045. 129 */ 130 public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { 131 Objects.requireNonNull(lineSeparator); 132 int[] base64 = Decoder.fromBase64; 133 for (byte b : lineSeparator) { 134 if (base64[b & 0xff] != -1) 135 throw new IllegalArgumentException( 136 "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); 137 } 138 if (lineLength <= 0) { 139 return Encoder.RFC4648; 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> The Base64 padding character {@code '='} is accepted and 618 * interpreted as the end of the encoded byte data, but is not 619 * required. So if the final unit of the encoded byte data only has 620 * two or three Base64 characters (without the corresponding padding 621 * character(s) padded), they are decoded as if followed by padding 622 * character(s). 623 * <p> 624 * For decoders that use the <a href="#basic">Basic</a> and 625 * <a href="#url">URL and Filename safe</a> type base64 scheme, and 626 * if there is padding character present in the final unit, the 627 * correct number of padding character(s) must be present, otherwise 628 * {@code IllegalArgumentException} ({@code IOException} when reading 629 * from a Base64 stream) is thrown during decoding. 630 * <p> 631 * Decoders that use the <a href="#mime">MIME</a> type base64 scheme 632 * are more lenient when decoding the padding character(s). If the 633 * padding character(s) is incorrectly encoded, the first padding 634 * character encountered is interpreted as the end of the encoded byte 635 * data, the decoding operation will then end and return normally. 636 * 637 * <p> Instances of {@link Decoder} class are safe for use by 638 * multiple concurrent threads. 639 * 640 * <p> Unless otherwise noted, passing a {@code null} argument to 641 * a method of this class will cause a 642 * {@link java.lang.NullPointerException NullPointerException} to 643 * be thrown. 644 * 645 * @see Encoder 646 * @since 1.8 647 */ 648 public static class Decoder { 649 650 private final boolean isURL; 651 private final boolean isMIME; 652 653 private Decoder(boolean isURL, boolean isMIME) { 654 this.isURL = isURL; 655 this.isMIME = isMIME; 656 } 657 658 /** 659 * Lookup table for decoding unicode characters drawn from the 660 * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 661 * their 6-bit positive integer equivalents. Characters that 662 * are not in the Base64 alphabet but fall within the bounds of 663 * the array are encoded to -1. 664 * 665 */ 666 private static final int[] fromBase64 = new int[256]; 667 static { 668 Arrays.fill(fromBase64, -1); 669 for (int i = 0; i < Encoder.toBase64.length; i++) 670 fromBase64[Encoder.toBase64[i]] = i; 671 fromBase64['='] = -2; 672 } 673 674 /** 675 * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 676 * as specified in Table2 of the RFC 4648. 677 */ 678 private static final int[] fromBase64URL = new int[256]; 679 680 static { 681 Arrays.fill(fromBase64URL, -1); 682 for (int i = 0; i < Encoder.toBase64URL.length; i++) 683 fromBase64URL[Encoder.toBase64URL[i]] = i; 684 fromBase64URL['='] = -2; 685 } 686 687 static final Decoder RFC4648 = new Decoder(false, false); 688 static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 689 static final Decoder RFC2045 = new Decoder(false, true); 690 691 /** 692 * Decodes all bytes from the input byte array using the {@link Base64} 693 * encoding scheme, writing the results into a newly-allocated output 694 * byte array. The returned byte array is of the length of the resulting 695 * bytes. 696 * 697 * @param src 698 * the byte array to decode 699 * 700 * @return A newly-allocated byte array containing the decoded bytes. 701 * 702 * @throws IllegalArgumentException 703 * if {@code src} is not in valid Base64 scheme 704 */ 705 public byte[] decode(byte[] src) { 706 byte[] dst = new byte[outLength(src, 0, src.length)]; 707 int ret = decode0(src, 0, src.length, dst); 708 if (ret != dst.length) { 709 dst = Arrays.copyOf(dst, ret); 710 } 711 return dst; 712 } 713 714 /** 715 * Decodes a Base64 encoded String into a newly-allocated byte array 716 * using the {@link Base64} encoding scheme. 717 * 718 * <p> An invocation of this method has exactly the same effect as invoking 719 * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} 720 * 721 * @param src 722 * the string to decode 723 * 724 * @return A newly-allocated byte array containing the decoded bytes. 725 * 726 * @throws IllegalArgumentException 727 * if {@code src} is not in valid Base64 scheme 728 */ 729 public byte[] decode(String src) { 730 return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 731 } 732 733 /** 734 * Decodes all bytes from the input byte array using the {@link Base64} 735 * encoding scheme, writing the results into the given output byte array, 736 * starting at offset 0. 737 * 738 * <p> It is the responsibility of the invoker of this method to make 739 * sure the output byte array {@code dst} has enough space for decoding 740 * all bytes from the input byte array. No bytes will be be written to 741 * the output byte array if the output byte array is not big enough. 742 * 743 * <p> If the input byte array is not in valid Base64 encoding scheme 744 * then some bytes may have been written to the output byte array before 745 * IllegalargumentException is thrown. 746 * 747 * @param src 748 * the byte array to decode 749 * @param dst 750 * the output byte array 751 * 752 * @return The number of bytes written to the output byte array 753 * 754 * @throws IllegalArgumentException 755 * if {@code src} is not in valid Base64 scheme, or {@code dst} 756 * does not have enough space for decoding all input bytes. 757 */ 758 public int decode(byte[] src, byte[] dst) { 759 int len = outLength(src, 0, src.length); 760 if (dst.length < len) 761 throw new IllegalArgumentException( 762 "Output byte array is too small for decoding all input bytes"); 763 return decode0(src, 0, src.length, dst); 764 } 765 766 /** 767 * Decodes all bytes from the input byte buffer using the {@link Base64} 768 * encoding scheme, writing the results into a newly-allocated ByteBuffer. 769 * 770 * <p> Upon return, the source buffer's position will be updated to 771 * its limit; its limit will not have been changed. The returned 772 * output buffer's position will be zero and its limit will be the 773 * number of resulting decoded bytes 774 * 775 * @param buffer 776 * the ByteBuffer to decode 777 * 778 * @return A newly-allocated byte buffer containing the decoded bytes 779 * 780 * @throws IllegalArgumentException 781 * if {@code src} is not in valid Base64 scheme. 782 */ 783 public ByteBuffer decode(ByteBuffer buffer) { 784 int pos0 = buffer.position(); 785 try { 786 byte[] src; 787 int sp, sl; 788 if (buffer.hasArray()) { 789 src = buffer.array(); 790 sp = buffer.arrayOffset() + buffer.position(); 791 sl = buffer.arrayOffset() + buffer.limit(); 792 buffer.position(buffer.limit()); 793 } else { 794 src = new byte[buffer.remaining()]; 795 buffer.get(src); 796 sp = 0; 797 sl = src.length; 798 } 799 byte[] dst = new byte[outLength(src, sp, sl)]; 800 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 801 } catch (IllegalArgumentException iae) { 802 buffer.position(pos0); 803 throw iae; 804 } 805 } 806 807 /** 808 * Decodes as many bytes as possible from the input byte buffer 809 * using the {@link Base64} encoding scheme, writing the resulting 810 * bytes to the given output byte buffer. 811 * 812 * <p>The buffers are read from, and written to, starting at their 813 * current positions. Upon return, the input and output buffers' 814 * positions will be advanced to reflect the bytes read and written, 815 * but their limits will not be modified. 816 * 817 * <p> If the input buffer is not in valid Base64 encoding scheme 818 * then some bytes may have been written to the output buffer 819 * before IllegalArgumentException is thrown. The positions of 820 * both input and output buffer will not be advanced in this case. 821 * 822 * <p>The decoding operation will end and return if all remaining 823 * bytes in the input buffer have been decoded and written to the 824 * output buffer. 825 * 826 * <p> The decoding operation will stop and return if the output 827 * buffer has insufficient space to decode any more input bytes. 828 * The decoding operation can be continued, if there is more bytes 829 * in input buffer to be decoded, by invoking this method again with 830 * an output buffer that has more {@linkplain java.nio.Buffer#remaining 831 * remaining} bytes. This is typically done by draining any decoded 832 * bytes from the output buffer. 833 * 834 * <p><b>Recommended Usage Example</b> 835 * <pre> 836 * ByteBuffer src = ...; 837 * ByteBuffer dst = ...; 838 * Base64.Decoder dec = Base64.getDecoder(); 839 * 840 * while (src.hasRemaining()) { 841 * 842 * // prepare the output byte buffer 843 * dst.clear(); 844 * dec.decode(src, dst); 845 * 846 * // read bytes from the output buffer 847 * dst.flip(); 848 * ... 849 * } 850 * </pre> 851 * 852 * @param src 853 * the input byte buffer to decode 854 * @param dst 855 * the output byte buffer 856 * 857 * @return The number of bytes written to the output byte buffer during 858 * this decoding invocation 859 * 860 * @throws IllegalArgumentException 861 * if {@code src} is not in valid Base64 scheme. 862 */ 863 public int decode(ByteBuffer src, ByteBuffer dst) { 864 int sp0 = src.position(); 865 int dp0 = dst.position(); 866 try { 867 if (src.hasArray() && dst.hasArray()) 868 return decodeArray(src, dst); 869 return decodeBuffer(src, dst); 870 } catch (IllegalArgumentException iae) { 871 src.position(sp0); 872 dst.position(dp0); 873 throw iae; 874 } 875 } 876 877 /** 878 * Returns an input stream for decoding {@link Base64} encoded byte stream. 879 * 880 * <p> The {@code read} methods of the returned {@code InputStream} will 881 * throw {@code IOException} when reading bytes that cannot be decoded. 882 * 883 * <p> Closing the returned input stream will close the underlying 884 * input stream. 885 * 886 * @param is 887 * the input stream 888 * 889 * @return the input stream for decoding the specified Base64 encoded 890 * byte stream 891 */ 892 public InputStream wrap(InputStream is) { 893 Objects.requireNonNull(is); 894 return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 895 } 896 897 private int decodeArray(ByteBuffer src, ByteBuffer dst) { 898 int[] base64 = isURL ? fromBase64URL : fromBase64; 899 int bits = 0; 900 int shiftto = 18; // pos of first byte of 4-byte atom 901 byte[] sa = src.array(); 902 int sp = src.arrayOffset() + src.position(); 903 int sl = src.arrayOffset() + src.limit(); 904 byte[] da = dst.array(); 905 int dp = dst.arrayOffset() + dst.position(); 906 int dl = dst.arrayOffset() + dst.limit(); 907 int dp0 = dp; 908 int mark = sp; 909 try { 910 while (sp < sl) { 911 int b = sa[sp++] & 0xff; 912 if ((b = base64[b]) < 0) { 913 if (b == -2) { // padding byte 914 if (!isMIME && 915 (shiftto == 6 && (sp == sl || sa[sp++] != '=') || 916 shiftto == 18)) { 917 throw new IllegalArgumentException( 918 "Input byte array has wrong 4-byte ending unit"); 919 } 920 break; 921 } 922 if (isMIME) // skip if for rfc2045 923 continue; 924 else 925 throw new IllegalArgumentException( 926 "Illegal base64 character " + 927 Integer.toString(sa[sp - 1], 16)); 928 } 929 bits |= (b << shiftto); 930 shiftto -= 6; 931 if (shiftto < 0) { 932 if (dl < dp + 3) 933 return dp - dp0; 934 da[dp++] = (byte)(bits >> 16); 935 da[dp++] = (byte)(bits >> 8); 936 da[dp++] = (byte)(bits); 937 shiftto = 18; 938 bits = 0; 939 mark = sp; 940 } 941 } 942 if (shiftto == 6) { 943 if (dl - dp < 1) 944 return dp - dp0; 945 da[dp++] = (byte)(bits >> 16); 946 } else if (shiftto == 0) { 947 if (dl - dp < 2) 948 return dp - dp0; 949 da[dp++] = (byte)(bits >> 16); 950 da[dp++] = (byte)(bits >> 8); 951 } else if (shiftto == 12) { 952 throw new IllegalArgumentException( 953 "Last unit does not have enough valid bits"); 954 } 955 if (sp < sl) { 956 if (isMIME) 957 sp = sl; 958 else 959 throw new IllegalArgumentException( 960 "Input byte array has incorrect ending byte at " + sp); 961 } 962 mark = sp; 963 return dp - dp0; 964 } finally { 965 src.position(mark); 966 dst.position(dp); 967 } 968 } 969 970 private int decodeBuffer(ByteBuffer src, ByteBuffer dst) { 971 int[] base64 = isURL ? fromBase64URL : fromBase64; 972 int bits = 0; 973 int shiftto = 18; // pos of first byte of 4-byte atom 974 int sp = src.position(); 975 int sl = src.limit(); 976 int dp = dst.position(); 977 int dl = dst.limit(); 978 int dp0 = dp; 979 int mark = sp; 980 try { 981 while (sp < sl) { 982 int b = src.get(sp++) & 0xff; 983 if ((b = base64[b]) < 0) { 984 if (b == -2) { // padding byte 985 if (!isMIME && 986 (shiftto == 6 && (sp == sl || src.get(sp++) != '=') || 987 shiftto == 18)) { 988 throw new IllegalArgumentException( 989 "Input byte array has wrong 4-byte ending unit"); 990 } 991 break; 992 } 993 if (isMIME) // skip if for rfc2045 994 continue; 995 else 996 throw new IllegalArgumentException( 997 "Illegal base64 character " + 998 Integer.toString(src.get(sp - 1), 16)); 999 } 1000 bits |= (b << shiftto); 1001 shiftto -= 6; 1002 if (shiftto < 0) { 1003 if (dl < dp + 3) 1004 return dp - dp0; 1005 dst.put(dp++, (byte)(bits >> 16)); 1006 dst.put(dp++, (byte)(bits >> 8)); 1007 dst.put(dp++, (byte)(bits)); 1008 shiftto = 18; 1009 bits = 0; 1010 mark = sp; 1011 } 1012 } 1013 if (shiftto == 6) { 1014 if (dl - dp < 1) 1015 return dp - dp0; 1016 dst.put(dp++, (byte)(bits >> 16)); 1017 } else if (shiftto == 0) { 1018 if (dl - dp < 2) 1019 return dp - dp0; 1020 dst.put(dp++, (byte)(bits >> 16)); 1021 dst.put(dp++, (byte)(bits >> 8)); 1022 } else if (shiftto == 12) { 1023 throw new IllegalArgumentException( 1024 "Last unit does not have enough valid bits"); 1025 } 1026 if (sp < sl) { 1027 if (isMIME) 1028 sp = sl; 1029 else 1030 throw new IllegalArgumentException( 1031 "Input byte array has incorrect ending byte at " + sp); 1032 } 1033 mark = sp; 1034 return dp - dp0; 1035 } finally { 1036 src.position(mark); 1037 dst.position(dp); 1038 } 1039 } 1040 1041 private int outLength(byte[] src, int sp, int sl) { 1042 int[] base64 = isURL ? fromBase64URL : fromBase64; 1043 int paddings = 0; 1044 int len = sl - sp; 1045 if (len == 0) 1046 return 0; 1047 if (len < 2) { 1048 if (isMIME && base64[0] == -1) 1049 return 0; 1050 throw new IllegalArgumentException( 1051 "Input byte[] should at least have 2 bytes for base64 bytes"); 1052 } 1053 if (isMIME) { 1054 // scan all bytes to fill out all non-alphabet. a performance 1055 // trade-off of pre-scan or Arrays.copyOf 1056 int n = 0; 1057 while (sp < sl) { 1058 int b = src[sp++] & 0xff; 1059 if (b == '=') { 1060 len -= (sl - sp + 1); 1061 break; 1062 } 1063 if ((b = base64[b]) == -1) 1064 n++; 1065 } 1066 len -= n; 1067 } else { 1068 if (src[sl - 1] == '=') { 1069 paddings++; 1070 if (src[sl - 2] == '=') 1071 paddings++; 1072 } 1073 } 1074 if (paddings == 0 && (len & 0x3) != 0) 1075 paddings = 4 - (len & 0x3); 1076 return 3 * ((len + 3) / 4) - paddings; 1077 } 1078 1079 private int decode0(byte[] src, int sp, int sl, byte[] dst) { 1080 int[] base64 = isURL ? fromBase64URL : fromBase64; 1081 int dp = 0; 1082 int bits = 0; 1083 int shiftto = 18; // pos of first byte of 4-byte atom 1084 while (sp < sl) { 1085 int b = src[sp++] & 0xff; 1086 if ((b = base64[b]) < 0) { 1087 if (b == -2) { // padding byte '=' 1088 if (!isMIME && // be lenient for rfc2045 1089 // = shiftto==18 unnecessary padding 1090 // x= shiftto==12 a dangling single x 1091 // x to be handled together with non-padding case 1092 // xx= shiftto==6&&sp==sl missing last = 1093 // xx=y shiftto==6 last is not = 1094 (shiftto == 6 && (sp == sl || src[sp++] != '=') || 1095 shiftto == 18)) { 1096 throw new IllegalArgumentException( 1097 "Input byte array has wrong 4-byte ending unit"); 1098 } 1099 break; 1100 } 1101 if (isMIME) // skip if for rfc2045 1102 continue; 1103 else 1104 throw new IllegalArgumentException( 1105 "Illegal base64 character " + 1106 Integer.toString(src[sp - 1], 16)); 1107 } 1108 bits |= (b << shiftto); 1109 shiftto -= 6; 1110 if (shiftto < 0) { 1111 dst[dp++] = (byte)(bits >> 16); 1112 dst[dp++] = (byte)(bits >> 8); 1113 dst[dp++] = (byte)(bits); 1114 shiftto = 18; 1115 bits = 0; 1116 } 1117 } 1118 // reached end of byte array or hit padding '=' characters. 1119 if (shiftto == 6) { 1120 dst[dp++] = (byte)(bits >> 16); 1121 } else if (shiftto == 0) { 1122 dst[dp++] = (byte)(bits >> 16); 1123 dst[dp++] = (byte)(bits >> 8); 1124 } else if (shiftto == 12) { 1125 // dangling single "x", throw exception even in lenient mode, 1126 // it's incorrectly encoded. 1127 throw new IllegalArgumentException( 1128 "Last unit does not have enough valid bits"); 1129 } 1130 // anything left is invalid, if is not MIME. 1131 // if MIME (lenient), just ignore all leftover 1132 if (sp < sl && !isMIME) { 1133 throw new IllegalArgumentException( 1134 "Input byte array has incorrect ending byte at " + sp); 1135 } 1136 return dp; 1137 } 1138 } 1139 1140 /* 1141 * An output stream for encoding bytes into the Base64. 1142 */ 1143 private static class EncOutputStream extends FilterOutputStream { 1144 1145 private int leftover = 0; 1146 private int b0, b1, b2; 1147 private boolean closed = false; 1148 1149 private final char[] base64; // byte->base64 mapping 1150 private final byte[] newline; // line separator, if needed 1151 private final int linemax; 1152 private int linepos = 0; 1153 1154 EncOutputStream(OutputStream os, 1155 char[] base64, byte[] newline, int linemax) { 1156 super(os); 1157 this.base64 = base64; 1158 this.newline = newline; 1159 this.linemax = linemax; 1160 } 1161 1162 @Override 1163 public void write(int b) throws IOException { 1164 byte[] buf = new byte[1]; 1165 buf[0] = (byte)(b & 0xff); 1166 write(buf, 0, 1); 1167 } 1168 1169 private void checkNewline() throws IOException { 1170 if (linepos == linemax) { 1171 out.write(newline); 1172 linepos = 0; 1173 } 1174 } 1175 1176 @Override 1177 public void write(byte[] b, int off, int len) throws IOException { 1178 if (closed) 1179 throw new IOException("Stream is closed"); 1180 if (off < 0 || len < 0 || off + len > b.length) 1181 throw new ArrayIndexOutOfBoundsException(); 1182 if (len == 0) 1183 return; 1184 if (leftover != 0) { 1185 if (leftover == 1) { 1186 b1 = b[off++] & 0xff; 1187 len--; 1188 if (len == 0) { 1189 leftover++; 1190 return; 1191 } 1192 } 1193 b2 = b[off++] & 0xff; 1194 len--; 1195 checkNewline(); 1196 out.write(base64[b0 >> 2]); 1197 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1198 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 1199 out.write(base64[b2 & 0x3f]); 1200 linepos += 4; 1201 } 1202 int nBits24 = len / 3; 1203 leftover = len - (nBits24 * 3); 1204 while (nBits24-- > 0) { 1205 checkNewline(); 1206 int bits = (b[off++] & 0xff) << 16 | 1207 (b[off++] & 0xff) << 8 | 1208 (b[off++] & 0xff); 1209 out.write(base64[(bits >>> 18) & 0x3f]); 1210 out.write(base64[(bits >>> 12) & 0x3f]); 1211 out.write(base64[(bits >>> 6) & 0x3f]); 1212 out.write(base64[bits & 0x3f]); 1213 linepos += 4; 1214 } 1215 if (leftover == 1) { 1216 b0 = b[off++] & 0xff; 1217 } else if (leftover == 2) { 1218 b0 = b[off++] & 0xff; 1219 b1 = b[off++] & 0xff; 1220 } 1221 } 1222 1223 @Override 1224 public void close() throws IOException { 1225 if (!closed) { 1226 closed = true; 1227 if (leftover == 1) { 1228 checkNewline(); 1229 out.write(base64[b0 >> 2]); 1230 out.write(base64[(b0 << 4) & 0x3f]); 1231 out.write('='); 1232 out.write('='); 1233 } else if (leftover == 2) { 1234 checkNewline(); 1235 out.write(base64[b0 >> 2]); 1236 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 1237 out.write(base64[(b1 << 2) & 0x3f]); 1238 out.write('='); 1239 } 1240 leftover = 0; 1241 out.close(); 1242 } 1243 } 1244 } 1245 1246 /* 1247 * An input stream for decoding Base64 bytes 1248 */ 1249 private static class DecInputStream extends InputStream { 1250 1251 private final InputStream is; 1252 private final boolean isMIME; 1253 private final int[] base64; // base64 -> byte mapping 1254 private int bits = 0; // 24-bit buffer for decoding 1255 private int nextin = 18; // next available "off" in "bits" for input; 1256 // -> 18, 12, 6, 0 1257 private int nextout = -8; // next available "off" in "bits" for output; 1258 // -> 8, 0, -8 (no byte for output) 1259 private boolean eof = false; 1260 private boolean closed = false; 1261 1262 DecInputStream(InputStream is, int[] base64, boolean isMIME) { 1263 this.is = is; 1264 this.base64 = base64; 1265 this.isMIME = isMIME; 1266 } 1267 1268 private byte[] sbBuf = new byte[1]; 1269 1270 @Override 1271 public int read() throws IOException { 1272 return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 1273 } 1274 1275 @Override 1276 public int read(byte[] b, int off, int len) throws IOException { 1277 if (closed) 1278 throw new IOException("Stream is closed"); 1279 if (eof && nextout < 0) // eof and no leftover 1280 return -1; 1281 if (off < 0 || len < 0 || len > b.length - off) 1282 throw new IndexOutOfBoundsException(); 1283 int oldOff = off; 1284 if (nextout >= 0) { // leftover output byte(s) in bits buf 1285 do { 1286 if (len == 0) 1287 return off - oldOff; 1288 b[off++] = (byte)(bits >> nextout); 1289 len--; 1290 nextout -= 8; 1291 } while (nextout >= 0); 1292 bits = 0; 1293 } 1294 while (len > 0) { 1295 int v = is.read(); 1296 if (v == -1) { 1297 eof = true; 1298 if (nextin != 18) { 1299 if (nextin == 12) 1300 throw new IOException("Base64 stream has one un-decoded dangling byte."); 1301 // treat ending xx/xxx without padding character legal. 1302 // same logic as v == '=' below 1303 b[off++] = (byte)(bits >> (16)); 1304 len--; 1305 if (nextin == 0) { // only one padding byte 1306 if (len == 0) { // no enough output space 1307 bits >>= 8; // shift to lowest byte 1308 nextout = 0; 1309 } else { 1310 b[off++] = (byte) (bits >> 8); 1311 } 1312 } 1313 } 1314 if (off == oldOff) 1315 return -1; 1316 else 1317 return off - oldOff; 1318 } 1319 if (v == '=') { // padding byte(s) 1320 // = shiftto==18 unnecessary padding 1321 // x= shiftto==12 dangling x, invalid unit 1322 // xx= shiftto==6 && missing last '=' 1323 // xx=y or last is not '=' 1324 if (nextin == 18 || nextin == 12 || 1325 nextin == 6 && is.read() != '=') { 1326 if (!isMIME || nextin == 12) { 1327 throw new IOException("Illegal base64 ending sequence:" + nextin); 1328 } else if (nextin != 18) { 1329 // lenient mode for mime 1330 // (1) handle the "unnecessary padding in "xxxx =" 1331 // case as the eof (nextin == 18) 1332 // (2) decode "xx=" and "xx=y" normally 1333 b[off++] = (byte)(bits >> (16)); 1334 len--; 1335 } 1336 } else { 1337 b[off++] = (byte)(bits >> (16)); 1338 len--; 1339 if (nextin == 0) { // only one padding byte 1340 if (len == 0) { // no enough output space 1341 bits >>= 8; // shift to lowest byte 1342 nextout = 0; 1343 } else { 1344 b[off++] = (byte) (bits >> 8); 1345 } 1346 } 1347 } 1348 eof = true; 1349 break; 1350 } 1351 if ((v = base64[v]) == -1) { 1352 if (isMIME) // skip if for rfc2045 1353 continue; 1354 else 1355 throw new IOException("Illegal base64 character " + 1356 Integer.toString(v, 16)); 1357 } 1358 bits |= (v << nextin); 1359 if (nextin == 0) { 1360 nextin = 18; // clear for next 1361 nextout = 16; 1362 while (nextout >= 0) { 1363 b[off++] = (byte)(bits >> nextout); 1364 len--; 1365 nextout -= 8; 1366 if (len == 0 && nextout >= 0) { // don't clean "bits" 1367 return off - oldOff; 1368 } 1369 } 1370 bits = 0; 1371 } else { 1372 nextin -= 6; 1373 } 1374 } 1375 return off - oldOff; 1376 } 1377 1378 @Override 1379 public int available() throws IOException { 1380 if (closed) 1381 throw new IOException("Stream is closed"); 1382 return is.available(); // TBD: 1383 } 1384 1385 @Override 1386 public void close() throws IOException { 1387 if (!closed) { 1388 closed = true; 1389 is.close(); 1390 } 1391 } 1392 } 1393 }