├── app
├── .gitignore
├── src
│ └── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ ├── activity_error.xml
│ │ │ └── activity_main.xml
│ │ └── menu
│ │ │ └── menu_main.xml
│ │ ├── java
│ │ └── com
│ │ │ └── haohaohu
│ │ │ └── cachemanagesample
│ │ │ ├── Test.java
│ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
├── build.gradle
└── proguard-rules.pro
├── library
├── .gitignore
├── project.properties
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ ├── com
│ │ └── haohaohu
│ │ │ └── cachemanage
│ │ │ ├── strategy
│ │ │ ├── IEncryptStrategy.java
│ │ │ ├── KeyStoreEncryptStrategy.java
│ │ │ └── Des3EncryptStrategy.java
│ │ │ ├── observer
│ │ │ ├── IDataChangeListener.java
│ │ │ └── CacheObserver.java
│ │ │ ├── util
│ │ │ ├── LockUtil.java
│ │ │ ├── Md5Utils.java
│ │ │ ├── Des3Util.java
│ │ │ ├── Base64Util.java
│ │ │ └── KeyStoreHelper.java
│ │ │ ├── CacheUtilConfig.java
│ │ │ ├── CacheUtil.java
│ │ │ └── ACache.java
│ │ └── mohapps
│ │ └── modified
│ │ └── java
│ │ └── util
│ │ └── Base64.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── .travis.yml
├── bintrayUpload.gradle
├── gradlew.bat
├── gradlew
├── README.md
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------
/library/project.properties:
--------------------------------------------------------------------------------
1 | #javadoc
2 | javadoc.name=CacheManage
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
Uses "The Base64 Alphabet" as specified in Table 1 of 51 | * RFC 4648 and RFC 2045 for encoding and decoding operation. 52 | * The encoder does not add any line feed (line separator) 53 | * character. The decoder rejects data that contains characters 54 | * outside the base64 alphabet.
Uses the "URL and Filename safe Base64 Alphabet" as specified 58 | * in Table 2 of RFC 4648 for encoding and decoding. The 59 | * encoder does not add any line feed (line separator) character. 60 | * The decoder rejects data that contains characters outside the 61 | * base64 alphabet.
Uses the "The Base64 Alphabet" as specified in Table 1 of 65 | * RFC 2045 for encoding and decoding operation. The encoded output 66 | * must be represented in lines of no more than 76 characters each 67 | * and uses a carriage return {@code '\r'} followed immediately by 68 | * a linefeed {@code '\n'} as the line separator. No line separator 69 | * is added to the end of the encoded output. All line separators 70 | * or other characters not found in the base64 alphabet table are 71 | * ignored in decoding operation.
Unless otherwise noted, passing a {@code null} argument to a 75 | * method of this class will cause a {@link java.lang.NullPointerException 76 | * NullPointerException} to be thrown. 77 | * 78 | * @author Xueming Shen 79 | * @since 1.8 80 | */ 81 | 82 | public class Base64 { 83 | 84 | private Base64() {} 85 | 86 | /** 87 | * Returns a {@link Encoder} that encodes using the 88 | * Basic type base64 encoding scheme. 89 | * 90 | * @return A Base64 encoder. 91 | */ 92 | public static Encoder getEncoder() { 93 | return Encoder.RFC4648; 94 | } 95 | 96 | /** 97 | * Returns a {@link Encoder} that encodes using the 98 | * URL and Filename safe type base64 99 | * encoding scheme. 100 | * 101 | * @return A Base64 encoder. 102 | */ 103 | public static Encoder getUrlEncoder() { 104 | return Encoder.RFC4648_URLSAFE; 105 | } 106 | 107 | /** 108 | * Returns a {@link Encoder} that encodes using the 109 | * MIME type base64 encoding scheme. 110 | * 111 | * @return A Base64 encoder. 112 | */ 113 | public static Encoder getMimeEncoder() { 114 | return Encoder.RFC2045; 115 | } 116 | 117 | /** 118 | * Returns a {@link Encoder} that encodes using the 119 | * MIME type base64 encoding scheme 120 | * with specified line length and line separators. 121 | * 122 | * @param lineLength 123 | * the length of each output line (rounded down to nearest multiple 124 | * of 4). If {@code lineLength <= 0} the output will not be separated 125 | * in lines 126 | * @param lineSeparator 127 | * the line separator for each output line 128 | * 129 | * @return A Base64 encoder. 130 | * 131 | * @throws IllegalArgumentException if {@code lineSeparator} includes any 132 | * character of "The Base64 Alphabet" as specified in Table 1 of 133 | * RFC 2045. 134 | */ 135 | public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { 136 | Objects.requireNonNull(lineSeparator); 137 | int[] base64 = Decoder.fromBase64; 138 | for (byte b : lineSeparator) { 139 | if (base64[b & 0xff] != -1) 140 | throw new IllegalArgumentException( 141 | "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); 142 | } 143 | if (lineLength <= 0) { 144 | return Encoder.RFC4648; 145 | } 146 | return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); 147 | } 148 | 149 | /** 150 | * Returns a {@link Decoder} that decodes using the 151 | * Basic type base64 encoding scheme. 152 | * 153 | * @return A Base64 decoder. 154 | */ 155 | public static Decoder getDecoder() { 156 | return Decoder.RFC4648; 157 | } 158 | 159 | /** 160 | * Returns a {@link Decoder} that decodes using the 161 | * URL and Filename safe type base64 162 | * encoding scheme. 163 | * 164 | * @return A Base64 decoder. 165 | */ 166 | public static Decoder getUrlDecoder() { 167 | return Decoder.RFC4648_URLSAFE; 168 | } 169 | 170 | /** 171 | * Returns a {@link Decoder} that decodes using the 172 | * MIME type base64 decoding scheme. 173 | * 174 | * @return A Base64 decoder. 175 | */ 176 | public static Decoder getMimeDecoder() { 177 | return Decoder.RFC2045; 178 | } 179 | 180 | /** 181 | * This class implements an encoder for encoding byte data using 182 | * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 183 | * 184 | *
Instances of {@link Encoder} class are safe for use by 185 | * multiple concurrent threads. 186 | * 187 | *
Unless otherwise noted, passing a {@code null} argument to 188 | * a method of this class will cause a 189 | * {@link java.lang.NullPointerException NullPointerException} to 190 | * be thrown. 191 | * 192 | * @see Decoder 193 | * @since 1.8 194 | */ 195 | public static class Encoder { 196 | 197 | private final byte[] newline; 198 | private final int linemax; 199 | private final boolean isURL; 200 | private final boolean doPadding; 201 | 202 | private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { 203 | this.isURL = isURL; 204 | this.newline = newline; 205 | this.linemax = linemax; 206 | this.doPadding = doPadding; 207 | } 208 | 209 | /** 210 | * This array is a lookup table that translates 6-bit positive integer 211 | * index values into their "Base64 Alphabet" equivalents as specified 212 | * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). 213 | */ 214 | private static final char[] toBase64 = { 215 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 216 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 217 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 218 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 219 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 220 | }; 221 | 222 | /** 223 | * It's the lookup table for "URL and Filename safe Base64" as specified 224 | * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and 225 | * '_'. This table is used when BASE64_URL is specified. 226 | */ 227 | private static final char[] toBase64URL = { 228 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 229 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 230 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 231 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 232 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' 233 | }; 234 | 235 | private static final int MIMELINEMAX = 76; 236 | private static final byte[] CRLF = new byte[] {'\r', '\n'}; 237 | 238 | static final Encoder RFC4648 = new Encoder(false, null, -1, true); 239 | static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); 240 | static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); 241 | 242 | private final int outLength(int srclen) { 243 | int len = 0; 244 | if (doPadding) { 245 | len = 4 * ((srclen + 2) / 3); 246 | } else { 247 | int n = srclen % 3; 248 | len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); 249 | } 250 | if (linemax > 0) // line separators 251 | len += (len - 1) / linemax * newline.length; 252 | return len; 253 | } 254 | 255 | /** 256 | * Encodes all bytes from the specified byte array into a newly-allocated 257 | * byte array using the {@link java.util.Base64} encoding scheme. The returned byte 258 | * array is of the length of the resulting bytes. 259 | * 260 | * @param src 261 | * the byte array to encode 262 | * @return A newly-allocated byte array containing the resulting 263 | * encoded bytes. 264 | */ 265 | public byte[] encode(byte[] src) { 266 | int len = outLength(src.length); // dst array size 267 | byte[] dst = new byte[len]; 268 | int ret = encode0(src, 0, src.length, dst); 269 | if (ret != dst.length) 270 | return Arrays.copyOf(dst, ret); 271 | return dst; 272 | } 273 | 274 | /** 275 | * Encodes all bytes from the specified byte array using the 276 | * {@link java.util.Base64} encoding scheme, writing the resulting bytes to the 277 | * given output byte array, starting at offset 0. 278 | * 279 | *
It is the responsibility of the invoker of this method to make 280 | * sure the output byte array {@code dst} has enough space for encoding 281 | * all bytes from the input byte array. No bytes will be written to the 282 | * output byte array if the output byte array is not big enough. 283 | * 284 | * @param src 285 | * the byte array to encode 286 | * @param dst 287 | * the output byte array 288 | * @return The number of bytes written to the output byte array 289 | * 290 | * @throws IllegalArgumentException if {@code dst} does not have enough 291 | * space for encoding all input bytes. 292 | */ 293 | public int encode(byte[] src, byte[] dst) { 294 | int len = outLength(src.length); // dst array size 295 | if (dst.length < len) 296 | throw new IllegalArgumentException( 297 | "Output byte array is too small for encoding all input bytes"); 298 | return encode0(src, 0, src.length, dst); 299 | } 300 | 301 | /** 302 | * Encodes the specified byte array into a String using the {@link java.util.Base64} 303 | * encoding scheme. 304 | * 305 | *
This method first encodes all input bytes into a base64 encoded 306 | * byte array and then constructs a new String by using the encoded byte 307 | * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 308 | * ISO-8859-1} charset. 309 | * 310 | *
In other words, an invocation of this method has exactly the same 311 | * effect as invoking 312 | * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. 313 | * 314 | * @param src 315 | * the byte array to encode 316 | * @return A String containing the resulting Base64 encoded characters 317 | */ 318 | @SuppressWarnings("deprecation") 319 | public String encodeToString(byte[] src) { 320 | byte[] encoded = encode(src); 321 | return new String(encoded, 0, 0, encoded.length); 322 | } 323 | 324 | /** 325 | * Encodes all remaining bytes from the specified byte buffer into 326 | * a newly-allocated ByteBuffer using the {@link java.util.Base64} encoding 327 | * scheme. 328 | * 329 | * Upon return, the source buffer's position will be updated to 330 | * its limit; its limit will not have been changed. The returned 331 | * output buffer's position will be zero and its limit will be the 332 | * number of resulting encoded bytes. 333 | * 334 | * @param buffer 335 | * the source ByteBuffer to encode 336 | * @return A newly-allocated byte buffer containing the encoded bytes. 337 | */ 338 | public ByteBuffer encode(ByteBuffer buffer) { 339 | int len = outLength(buffer.remaining()); 340 | byte[] dst = new byte[len]; 341 | int ret = 0; 342 | if (buffer.hasArray()) { 343 | ret = encode0(buffer.array(), 344 | buffer.arrayOffset() + buffer.position(), 345 | buffer.arrayOffset() + buffer.limit(), 346 | dst); 347 | buffer.position(buffer.limit()); 348 | } else { 349 | byte[] src = new byte[buffer.remaining()]; 350 | buffer.get(src); 351 | ret = encode0(src, 0, src.length, dst); 352 | } 353 | if (ret != dst.length) 354 | dst = Arrays.copyOf(dst, ret); 355 | return ByteBuffer.wrap(dst); 356 | } 357 | 358 | /** 359 | * Wraps an output stream for encoding byte data using the {@link java.util.Base64} 360 | * encoding scheme. 361 | * 362 | *
It is recommended to promptly close the returned output stream after 363 | * use, during which it will flush all possible leftover bytes to the underlying 364 | * output stream. Closing the returned output stream will close the underlying 365 | * output stream. 366 | * 367 | * @param os 368 | * the output stream. 369 | * @return the output stream for encoding the byte data into the 370 | * specified Base64 encoded format 371 | */ 372 | public OutputStream wrap(OutputStream os) { 373 | Objects.requireNonNull(os); 374 | return new EncOutputStream(os, isURL ? toBase64URL : toBase64, 375 | newline, linemax, doPadding); 376 | } 377 | 378 | /** 379 | * Returns an encoder instance that encodes equivalently to this one, 380 | * but without adding any padding character at the end of the encoded 381 | * byte data. 382 | * 383 | *
The encoding scheme of this encoder instance is unaffected by 384 | * this invocation. The returned encoder instance should be used for 385 | * non-padding encoding operation. 386 | * 387 | * @return an equivalent encoder that encodes without adding any 388 | * padding character at the end 389 | */ 390 | public Encoder withoutPadding() { 391 | if (!doPadding) 392 | return this; 393 | return new Encoder(isURL, newline, linemax, false); 394 | } 395 | 396 | private int encode0(byte[] src, int off, int end, byte[] dst) { 397 | char[] base64 = isURL ? toBase64URL : toBase64; 398 | int sp = off; 399 | int slen = (end - off) / 3 * 3; 400 | int sl = off + slen; 401 | if (linemax > 0 && slen > linemax / 4 * 3) 402 | slen = linemax / 4 * 3; 403 | int dp = 0; 404 | while (sp < sl) { 405 | int sl0 = Math.min(sp + slen, sl); 406 | for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 407 | int bits = (src[sp0++] & 0xff) << 16 | 408 | (src[sp0++] & 0xff) << 8 | 409 | (src[sp0++] & 0xff); 410 | dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 411 | dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 412 | dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 413 | dst[dp0++] = (byte)base64[bits & 0x3f]; 414 | } 415 | int dlen = (sl0 - sp) / 3 * 4; 416 | dp += dlen; 417 | sp = sl0; 418 | if (dlen == linemax && sp < end) { 419 | for (byte b : newline){ 420 | dst[dp++] = b; 421 | } 422 | } 423 | } 424 | if (sp < end) { // 1 or 2 leftover bytes 425 | int b0 = src[sp++] & 0xff; 426 | dst[dp++] = (byte)base64[b0 >> 2]; 427 | if (sp == end) { 428 | dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 429 | if (doPadding) { 430 | dst[dp++] = '='; 431 | dst[dp++] = '='; 432 | } 433 | } else { 434 | int b1 = src[sp++] & 0xff; 435 | dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 436 | dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 437 | if (doPadding) { 438 | dst[dp++] = '='; 439 | } 440 | } 441 | } 442 | return dp; 443 | } 444 | } 445 | 446 | /** 447 | * This class implements a decoder for decoding byte data using the 448 | * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 449 | * 450 | *
The Base64 padding character {@code '='} is accepted and 451 | * interpreted as the end of the encoded byte data, but is not 452 | * required. So if the final unit of the encoded byte data only has 453 | * two or three Base64 characters (without the corresponding padding 454 | * character(s) padded), they are decoded as if followed by padding 455 | * character(s). If there is a padding character present in the 456 | * final unit, the correct number of padding character(s) must be 457 | * present, otherwise {@code IllegalArgumentException} ( 458 | * {@code IOException} when reading from a Base64 stream) is thrown 459 | * during decoding. 460 | * 461 | *
Instances of {@link Decoder} class are safe for use by 462 | * multiple concurrent threads. 463 | * 464 | *
Unless otherwise noted, passing a {@code null} argument to 465 | * a method of this class will cause a 466 | * {@link java.lang.NullPointerException NullPointerException} to 467 | * be thrown. 468 | * 469 | * @see Encoder 470 | * @since 1.8 471 | */ 472 | public static class Decoder { 473 | 474 | private final boolean isURL; 475 | private final boolean isMIME; 476 | 477 | private Decoder(boolean isURL, boolean isMIME) { 478 | this.isURL = isURL; 479 | this.isMIME = isMIME; 480 | } 481 | 482 | /** 483 | * Lookup table for decoding unicode characters drawn from the 484 | * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 485 | * their 6-bit positive integer equivalents. Characters that 486 | * are not in the Base64 alphabet but fall within the bounds of 487 | * the array are encoded to -1. 488 | * 489 | */ 490 | private static final int[] fromBase64 = new int[256]; 491 | static { 492 | Arrays.fill(fromBase64, -1); 493 | for (int i = 0; i < Encoder.toBase64.length; i++) 494 | fromBase64[Encoder.toBase64[i]] = i; 495 | fromBase64['='] = -2; 496 | } 497 | 498 | /** 499 | * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 500 | * as specified in Table2 of the RFC 4648. 501 | */ 502 | private static final int[] fromBase64URL = new int[256]; 503 | 504 | static { 505 | Arrays.fill(fromBase64URL, -1); 506 | for (int i = 0; i < Encoder.toBase64URL.length; i++) 507 | fromBase64URL[Encoder.toBase64URL[i]] = i; 508 | fromBase64URL['='] = -2; 509 | } 510 | 511 | static final Decoder RFC4648 = new Decoder(false, false); 512 | static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 513 | static final Decoder RFC2045 = new Decoder(false, true); 514 | 515 | /** 516 | * Decodes all bytes from the input byte array using the {@link java.util.Base64} 517 | * encoding scheme, writing the results into a newly-allocated output 518 | * byte array. The returned byte array is of the length of the resulting 519 | * bytes. 520 | * 521 | * @param src 522 | * the byte array to decode 523 | * 524 | * @return A newly-allocated byte array containing the decoded bytes. 525 | * 526 | * @throws IllegalArgumentException 527 | * if {@code src} is not in valid Base64 scheme 528 | */ 529 | public byte[] decode(byte[] src) { 530 | byte[] dst = new byte[outLength(src, 0, src.length)]; 531 | int ret = decode0(src, 0, src.length, dst); 532 | if (ret != dst.length) { 533 | dst = Arrays.copyOf(dst, ret); 534 | } 535 | return dst; 536 | } 537 | 538 | /** 539 | * Decodes a Base64 encoded String into a newly-allocated byte array 540 | * using the {@link java.util.Base64} encoding scheme. 541 | * 542 | *
An invocation of this method has exactly the same effect as invoking 543 | * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} 544 | * 545 | * @param src 546 | * the string to decode 547 | * 548 | * @return A newly-allocated byte array containing the decoded bytes. 549 | * 550 | * @throws IllegalArgumentException 551 | * if {@code src} is not in valid Base64 scheme 552 | */ 553 | public byte[] decode(String src) { 554 | return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 555 | } 556 | 557 | /** 558 | * Decodes all bytes from the input byte array using the {@link java.util.Base64} 559 | * encoding scheme, writing the results into the given output byte array, 560 | * starting at offset 0. 561 | * 562 | *
It is the responsibility of the invoker of this method to make 563 | * sure the output byte array {@code dst} has enough space for decoding 564 | * all bytes from the input byte array. No bytes will be be written to 565 | * the output byte array if the output byte array is not big enough. 566 | * 567 | *
If the input byte array is not in valid Base64 encoding scheme 568 | * then some bytes may have been written to the output byte array before 569 | * IllegalargumentException is thrown. 570 | * 571 | * @param src 572 | * the byte array to decode 573 | * @param dst 574 | * the output byte array 575 | * 576 | * @return The number of bytes written to the output byte array 577 | * 578 | * @throws IllegalArgumentException 579 | * if {@code src} is not in valid Base64 scheme, or {@code dst} 580 | * does not have enough space for decoding all input bytes. 581 | */ 582 | public int decode(byte[] src, byte[] dst) { 583 | int len = outLength(src, 0, src.length); 584 | if (dst.length < len) 585 | throw new IllegalArgumentException( 586 | "Output byte array is too small for decoding all input bytes"); 587 | return decode0(src, 0, src.length, dst); 588 | } 589 | 590 | /** 591 | * Decodes all bytes from the input byte buffer using the {@link java.util.Base64} 592 | * encoding scheme, writing the results into a newly-allocated ByteBuffer. 593 | * 594 | *
Upon return, the source buffer's position will be updated to 595 | * its limit; its limit will not have been changed. The returned 596 | * output buffer's position will be zero and its limit will be the 597 | * number of resulting decoded bytes 598 | * 599 | *
{@code IllegalArgumentException} is thrown if the input buffer 600 | * is not in valid Base64 encoding scheme. The position of the input 601 | * buffer will not be advanced in this case. 602 | * 603 | * @param buffer 604 | * the ByteBuffer to decode 605 | * 606 | * @return A newly-allocated byte buffer containing the decoded bytes 607 | * 608 | * @throws IllegalArgumentException 609 | * if {@code src} is not in valid Base64 scheme. 610 | */ 611 | public ByteBuffer decode(ByteBuffer buffer) { 612 | int pos0 = buffer.position(); 613 | try { 614 | byte[] src; 615 | int sp, sl; 616 | if (buffer.hasArray()) { 617 | src = buffer.array(); 618 | sp = buffer.arrayOffset() + buffer.position(); 619 | sl = buffer.arrayOffset() + buffer.limit(); 620 | buffer.position(buffer.limit()); 621 | } else { 622 | src = new byte[buffer.remaining()]; 623 | buffer.get(src); 624 | sp = 0; 625 | sl = src.length; 626 | } 627 | byte[] dst = new byte[outLength(src, sp, sl)]; 628 | return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 629 | } catch (IllegalArgumentException iae) { 630 | buffer.position(pos0); 631 | throw iae; 632 | } 633 | } 634 | 635 | /** 636 | * Returns an input stream for decoding {@link java.util.Base64} encoded byte stream. 637 | * 638 | *
The {@code read} methods of the returned {@code InputStream} will 639 | * throw {@code IOException} when reading bytes that cannot be decoded. 640 | * 641 | *
Closing the returned input stream will close the underlying 642 | * input stream. 643 | * 644 | * @param is 645 | * the input stream 646 | * 647 | * @return the input stream for decoding the specified Base64 encoded 648 | * byte stream 649 | */ 650 | public InputStream wrap(InputStream is) { 651 | Objects.requireNonNull(is); 652 | return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 653 | } 654 | 655 | private int outLength(byte[] src, int sp, int sl) { 656 | int[] base64 = isURL ? fromBase64URL : fromBase64; 657 | int paddings = 0; 658 | int len = sl - sp; 659 | if (len == 0) 660 | return 0; 661 | if (len < 2) { 662 | if (isMIME && base64[0] == -1) 663 | return 0; 664 | throw new IllegalArgumentException( 665 | "Input byte[] should at least have 2 bytes for base64 bytes"); 666 | } 667 | if (isMIME) { 668 | // scan all bytes to fill out all non-alphabet. a performance 669 | // trade-off of pre-scan or Arrays.copyOf 670 | int n = 0; 671 | while (sp < sl) { 672 | int b = src[sp++] & 0xff; 673 | if (b == '=') { 674 | len -= (sl - sp + 1); 675 | break; 676 | } 677 | if ((b = base64[b]) == -1) 678 | n++; 679 | } 680 | len -= n; 681 | } else { 682 | if (src[sl - 1] == '=') { 683 | paddings++; 684 | if (src[sl - 2] == '=') 685 | paddings++; 686 | } 687 | } 688 | if (paddings == 0 && (len & 0x3) != 0) 689 | paddings = 4 - (len & 0x3); 690 | return 3 * ((len + 3) / 4) - paddings; 691 | } 692 | 693 | private int decode0(byte[] src, int sp, int sl, byte[] dst) { 694 | int[] base64 = isURL ? fromBase64URL : fromBase64; 695 | int dp = 0; 696 | int bits = 0; 697 | int shiftto = 18; // pos of first byte of 4-byte atom 698 | while (sp < sl) { 699 | int b = src[sp++] & 0xff; 700 | if ((b = base64[b]) < 0) { 701 | if (b == -2) { // padding byte '=' 702 | // = shiftto==18 unnecessary padding 703 | // x= shiftto==12 a dangling single x 704 | // x to be handled together with non-padding case 705 | // xx= shiftto==6&&sp==sl missing last = 706 | // xx=y shiftto==6 last is not = 707 | if (shiftto == 6 && (sp == sl || src[sp++] != '=') || 708 | shiftto == 18) { 709 | throw new IllegalArgumentException( 710 | "Input byte array has wrong 4-byte ending unit"); 711 | } 712 | break; 713 | } 714 | if (isMIME) // skip if for rfc2045 715 | continue; 716 | else 717 | throw new IllegalArgumentException( 718 | "Illegal base64 character " + 719 | Integer.toString(src[sp - 1], 16)); 720 | } 721 | bits |= (b << shiftto); 722 | shiftto -= 6; 723 | if (shiftto < 0) { 724 | dst[dp++] = (byte)(bits >> 16); 725 | dst[dp++] = (byte)(bits >> 8); 726 | dst[dp++] = (byte)(bits); 727 | shiftto = 18; 728 | bits = 0; 729 | } 730 | } 731 | // reached end of byte array or hit padding '=' characters. 732 | if (shiftto == 6) { 733 | dst[dp++] = (byte)(bits >> 16); 734 | } else if (shiftto == 0) { 735 | dst[dp++] = (byte)(bits >> 16); 736 | dst[dp++] = (byte)(bits >> 8); 737 | } else if (shiftto == 12) { 738 | // dangling single "x", incorrectly encoded. 739 | throw new IllegalArgumentException( 740 | "Last unit does not have enough valid bits"); 741 | } 742 | // anything left is invalid, if is not MIME. 743 | // if MIME, ignore all non-base64 character 744 | while (sp < sl) { 745 | if (isMIME && base64[src[sp++]] < 0) 746 | continue; 747 | throw new IllegalArgumentException( 748 | "Input byte array has incorrect ending byte at " + sp); 749 | } 750 | return dp; 751 | } 752 | } 753 | 754 | /* 755 | * An output stream for encoding bytes into the Base64. 756 | */ 757 | private static class EncOutputStream extends FilterOutputStream { 758 | 759 | private int leftover = 0; 760 | private int b0, b1, b2; 761 | private boolean closed = false; 762 | 763 | private final char[] base64; // byte->base64 mapping 764 | private final byte[] newline; // line separator, if needed 765 | private final int linemax; 766 | private final boolean doPadding;// whether or not to pad 767 | private int linepos = 0; 768 | 769 | EncOutputStream(OutputStream os, char[] base64, 770 | byte[] newline, int linemax, boolean doPadding) { 771 | super(os); 772 | this.base64 = base64; 773 | this.newline = newline; 774 | this.linemax = linemax; 775 | this.doPadding = doPadding; 776 | } 777 | 778 | @Override 779 | public void write(int b) throws IOException { 780 | byte[] buf = new byte[1]; 781 | buf[0] = (byte)(b & 0xff); 782 | write(buf, 0, 1); 783 | } 784 | 785 | private void checkNewline() throws IOException { 786 | if (linepos == linemax) { 787 | out.write(newline); 788 | linepos = 0; 789 | } 790 | } 791 | 792 | @Override 793 | public void write(byte[] b, int off, int len) throws IOException { 794 | if (closed) 795 | throw new IOException("Stream is closed"); 796 | if (off < 0 || len < 0 || len > b.length - off) 797 | throw new ArrayIndexOutOfBoundsException(); 798 | if (len == 0) 799 | return; 800 | if (leftover != 0) { 801 | if (leftover == 1) { 802 | b1 = b[off++] & 0xff; 803 | len--; 804 | if (len == 0) { 805 | leftover++; 806 | return; 807 | } 808 | } 809 | b2 = b[off++] & 0xff; 810 | len--; 811 | checkNewline(); 812 | out.write(base64[b0 >> 2]); 813 | out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 814 | out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 815 | out.write(base64[b2 & 0x3f]); 816 | linepos += 4; 817 | } 818 | int nBits24 = len / 3; 819 | leftover = len - (nBits24 * 3); 820 | while (nBits24-- > 0) { 821 | checkNewline(); 822 | int bits = (b[off++] & 0xff) << 16 | 823 | (b[off++] & 0xff) << 8 | 824 | (b[off++] & 0xff); 825 | out.write(base64[(bits >>> 18) & 0x3f]); 826 | out.write(base64[(bits >>> 12) & 0x3f]); 827 | out.write(base64[(bits >>> 6) & 0x3f]); 828 | out.write(base64[bits & 0x3f]); 829 | linepos += 4; 830 | } 831 | if (leftover == 1) { 832 | b0 = b[off++] & 0xff; 833 | } else if (leftover == 2) { 834 | b0 = b[off++] & 0xff; 835 | b1 = b[off++] & 0xff; 836 | } 837 | } 838 | 839 | @Override 840 | public void close() throws IOException { 841 | if (!closed) { 842 | closed = true; 843 | if (leftover == 1) { 844 | checkNewline(); 845 | out.write(base64[b0 >> 2]); 846 | out.write(base64[(b0 << 4) & 0x3f]); 847 | if (doPadding) { 848 | out.write('='); 849 | out.write('='); 850 | } 851 | } else if (leftover == 2) { 852 | checkNewline(); 853 | out.write(base64[b0 >> 2]); 854 | out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 855 | out.write(base64[(b1 << 2) & 0x3f]); 856 | if (doPadding) { 857 | out.write('='); 858 | } 859 | } 860 | leftover = 0; 861 | out.close(); 862 | } 863 | } 864 | } 865 | 866 | /* 867 | * An input stream for decoding Base64 bytes 868 | */ 869 | private static class DecInputStream extends InputStream { 870 | 871 | private final InputStream is; 872 | private final boolean isMIME; 873 | private final int[] base64; // base64 -> byte mapping 874 | private int bits = 0; // 24-bit buffer for decoding 875 | private int nextin = 18; // next available "off" in "bits" for input; 876 | // -> 18, 12, 6, 0 877 | private int nextout = -8; // next available "off" in "bits" for output; 878 | // -> 8, 0, -8 (no byte for output) 879 | private boolean eof = false; 880 | private boolean closed = false; 881 | 882 | DecInputStream(InputStream is, int[] base64, boolean isMIME) { 883 | this.is = is; 884 | this.base64 = base64; 885 | this.isMIME = isMIME; 886 | } 887 | 888 | private byte[] sbBuf = new byte[1]; 889 | 890 | @Override 891 | public int read() throws IOException { 892 | return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 893 | } 894 | 895 | @Override 896 | public int read(byte[] b, int off, int len) throws IOException { 897 | if (closed) 898 | throw new IOException("Stream is closed"); 899 | if (eof && nextout < 0) // eof and no leftover 900 | return -1; 901 | if (off < 0 || len < 0 || len > b.length - off) 902 | throw new IndexOutOfBoundsException(); 903 | int oldOff = off; 904 | if (nextout >= 0) { // leftover output byte(s) in bits buf 905 | do { 906 | if (len == 0) 907 | return off - oldOff; 908 | b[off++] = (byte)(bits >> nextout); 909 | len--; 910 | nextout -= 8; 911 | } while (nextout >= 0); 912 | bits = 0; 913 | } 914 | while (len > 0) { 915 | int v = is.read(); 916 | if (v == -1) { 917 | eof = true; 918 | if (nextin != 18) { 919 | if (nextin == 12) 920 | throw new IOException("Base64 stream has one un-decoded dangling byte."); 921 | // treat ending xx/xxx without padding character legal. 922 | // same logic as v == '=' below 923 | b[off++] = (byte)(bits >> (16)); 924 | len--; 925 | if (nextin == 0) { // only one padding byte 926 | if (len == 0) { // no enough output space 927 | bits >>= 8; // shift to lowest byte 928 | nextout = 0; 929 | } else { 930 | b[off++] = (byte) (bits >> 8); 931 | } 932 | } 933 | } 934 | if (off == oldOff) 935 | return -1; 936 | else 937 | return off - oldOff; 938 | } 939 | if (v == '=') { // padding byte(s) 940 | // = shiftto==18 unnecessary padding 941 | // x= shiftto==12 dangling x, invalid unit 942 | // xx= shiftto==6 && missing last '=' 943 | // xx=y or last is not '=' 944 | if (nextin == 18 || nextin == 12 || 945 | nextin == 6 && is.read() != '=') { 946 | throw new IOException("Illegal base64 ending sequence:" + nextin); 947 | } 948 | b[off++] = (byte)(bits >> (16)); 949 | len--; 950 | if (nextin == 0) { // only one padding byte 951 | if (len == 0) { // no enough output space 952 | bits >>= 8; // shift to lowest byte 953 | nextout = 0; 954 | } else { 955 | b[off++] = (byte) (bits >> 8); 956 | } 957 | } 958 | eof = true; 959 | break; 960 | } 961 | if ((v = base64[v]) == -1) { 962 | if (isMIME) // skip if for rfc2045 963 | continue; 964 | else 965 | throw new IOException("Illegal base64 character " + 966 | Integer.toString(v, 16)); 967 | } 968 | bits |= (v << nextin); 969 | if (nextin == 0) { 970 | nextin = 18; // clear for next 971 | nextout = 16; 972 | while (nextout >= 0) { 973 | b[off++] = (byte)(bits >> nextout); 974 | len--; 975 | nextout -= 8; 976 | if (len == 0 && nextout >= 0) { // don't clean "bits" 977 | return off - oldOff; 978 | } 979 | } 980 | bits = 0; 981 | } else { 982 | nextin -= 6; 983 | } 984 | } 985 | return off - oldOff; 986 | } 987 | 988 | @Override 989 | public int available() throws IOException { 990 | if (closed) 991 | throw new IOException("Stream is closed"); 992 | return is.available(); // TBD: 993 | } 994 | 995 | @Override 996 | public void close() throws IOException { 997 | if (!closed) { 998 | closed = true; 999 | is.close(); 1000 | } 1001 | } 1002 | } 1003 | } 1004 | 1005 | --------------------------------------------------------------------------------