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