001/* 002 * Copyright (C) 2012 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.io; 016 017import static com.google.common.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkNotNull; 019import static com.google.common.base.Preconditions.checkPositionIndexes; 020import static com.google.common.base.Preconditions.checkState; 021import static com.google.common.math.IntMath.divide; 022import static com.google.common.math.IntMath.log2; 023import static java.lang.Math.max; 024import static java.lang.Math.min; 025import static java.lang.System.arraycopy; 026import static java.math.RoundingMode.CEILING; 027import static java.math.RoundingMode.FLOOR; 028import static java.math.RoundingMode.UNNECESSARY; 029 030import com.google.common.annotations.GwtCompatible; 031import com.google.common.annotations.GwtIncompatible; 032import com.google.common.annotations.J2ktIncompatible; 033import com.google.common.base.Ascii; 034import com.google.errorprone.annotations.concurrent.GuardedBy; 035import com.google.errorprone.annotations.concurrent.LazyInit; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.io.Reader; 040import java.io.Writer; 041import java.util.Arrays; 042import java.util.Objects; 043import org.jspecify.annotations.Nullable; 044 045/** 046 * A binary encoding scheme for reversibly translating between byte sequences and printable ASCII 047 * strings. This class includes several constants for encoding schemes specified by <a 048 * href="http://tools.ietf.org/html/rfc4648">RFC 4648</a>. For example, the expression: 049 * 050 * {@snippet : 051 * BaseEncoding.base32().encode("foo".getBytes(US_ASCII)) 052 * } 053 * 054 * <p>returns the string {@code "MZXW6==="}, and 055 * 056 * {@snippet : 057 * byte[] decoded = BaseEncoding.base32().decode("MZXW6==="); 058 * } 059 * 060 * <p>...returns the ASCII bytes of the string {@code "foo"}. 061 * 062 * <p>By default, {@code BaseEncoding}'s behavior is relatively strict and in accordance with RFC 063 * 4648. Decoding rejects characters in the wrong case, though padding is optional. To modify 064 * encoding and decoding behavior, use configuration methods to obtain a new encoding with modified 065 * behavior: 066 * 067 * {@snippet : 068 * BaseEncoding.base16().lowerCase().decode("deadbeef"); 069 * } 070 * 071 * <p>Warning: BaseEncoding instances are immutable. Invoking a configuration method has no effect 072 * on the receiving instance; you must store and use the new encoding instance it returns, instead. 073 * 074 * {@snippet : 075 * // Do NOT do this 076 * BaseEncoding hex = BaseEncoding.base16(); 077 * hex.lowerCase(); // does nothing! 078 * return hex.decode("deadbeef"); // throws an IllegalArgumentException 079 * } 080 * 081 * <p>It is guaranteed that {@code encoding.decode(encoding.encode(x))} is always equal to {@code 082 * x}, but the reverse does not necessarily hold. 083 * 084 * <table> 085 * <caption>Encodings</caption> 086 * <tr> 087 * <th>Encoding 088 * <th>Alphabet 089 * <th>{@code char:byte} ratio 090 * <th>Default padding 091 * <th>Comments 092 * <tr> 093 * <td>{@link #base16()} 094 * <td>0-9 A-F 095 * <td>2.00 096 * <td>N/A 097 * <td>Traditional hexadecimal. Defaults to upper case. 098 * <tr> 099 * <td>{@link #base32()} 100 * <td>A-Z 2-7 101 * <td>1.60 102 * <td>= 103 * <td>Human-readable; no possibility of mixing up 0/O or 1/I. Defaults to upper case. 104 * <tr> 105 * <td>{@link #base32Hex()} 106 * <td>0-9 A-V 107 * <td>1.60 108 * <td>= 109 * <td>"Numerical" base 32; extended from the traditional hex alphabet. Defaults to upper case. 110 * <tr> 111 * <td>{@link #base64()} 112 * <td>A-Z a-z 0-9 + / 113 * <td>1.33 114 * <td>= 115 * <td> 116 * <tr> 117 * <td>{@link #base64Url()} 118 * <td>A-Z a-z 0-9 - _ 119 * <td>1.33 120 * <td>= 121 * <td>Safe to use as filenames, or to pass in URLs without escaping 122 * </table> 123 * 124 * <p>All instances of this class are immutable, so they may be stored safely as static constants. 125 * 126 * @author Louis Wasserman 127 * @since 14.0 128 */ 129// We use `& 0xff` before casting to `(byte)` to keep GWT from letting the byte be out of range: 130// https://www.gwtproject.org/doc/latest/DevGuideCodingBasicsCompatibility 131@SuppressWarnings("MaskAndCastToByte") 132@GwtCompatible 133public abstract class BaseEncoding { 134 // TODO(lowasser): consider making encodeTo(Appendable, byte[], int, int) public. 135 136 BaseEncoding() {} 137 138 /** 139 * Exception indicating invalid base-encoded input encountered while decoding. 140 * 141 * @author Louis Wasserman 142 * @since 15.0 143 */ 144 public static final class DecodingException extends IOException { 145 DecodingException(@Nullable String message) { 146 super(message); 147 } 148 } 149 150 /** Encodes the specified byte array, and returns the encoded {@code String}. */ 151 public String encode(byte[] bytes) { 152 return encode(bytes, 0, bytes.length); 153 } 154 155 /** 156 * Encodes the specified range of the specified byte array, and returns the encoded {@code 157 * String}. 158 */ 159 public final String encode(byte[] bytes, int off, int len) { 160 checkPositionIndexes(off, off + len, bytes.length); 161 StringBuilder result = new StringBuilder(maxEncodedSize(len)); 162 try { 163 encodeTo(result, bytes, off, len); 164 } catch (IOException impossible) { 165 throw new AssertionError(impossible); 166 } 167 return result.toString(); 168 } 169 170 /** 171 * Returns an {@code OutputStream} that encodes bytes using this encoding into the specified 172 * {@code Writer}. When the returned {@code OutputStream} is closed, so is the backing {@code 173 * Writer}. 174 */ 175 @J2ktIncompatible 176 @GwtIncompatible // Writer,OutputStream 177 public abstract OutputStream encodingStream(Writer writer); 178 179 /** 180 * Returns a {@code ByteSink} that writes base-encoded bytes to the specified {@code CharSink}. 181 */ 182 @J2ktIncompatible 183 @GwtIncompatible // ByteSink,CharSink 184 public final ByteSink encodingSink(CharSink encodedSink) { 185 checkNotNull(encodedSink); 186 return new ByteSink() { 187 @Override 188 public OutputStream openStream() throws IOException { 189 return encodingStream(encodedSink.openStream()); 190 } 191 }; 192 } 193 194 // TODO(lowasser): document the extent of leniency, probably after adding ignore(CharMatcher) 195 196 private static byte[] extract(byte[] result, int length) { 197 if (length == result.length) { 198 return result; 199 } 200 byte[] trunc = new byte[length]; 201 arraycopy(result, 0, trunc, 0, length); 202 return trunc; 203 } 204 205 /** 206 * Determines whether the specified character sequence is a valid encoded string according to this 207 * encoding. 208 * 209 * @since 20.0 210 */ 211 public abstract boolean canDecode(CharSequence chars); 212 213 /** 214 * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the 215 * inverse operation to {@link #encode(byte[])}. 216 * 217 * @throws IllegalArgumentException if the input is not a valid encoded string according to this 218 * encoding. 219 */ 220 public final byte[] decode(CharSequence chars) { 221 try { 222 return decodeChecked(chars); 223 } catch (DecodingException badInput) { 224 throw new IllegalArgumentException(badInput); 225 } 226 } 227 228 /** 229 * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the 230 * inverse operation to {@link #encode(byte[])}. 231 * 232 * @throws DecodingException if the input is not a valid encoded string according to this 233 * encoding. 234 */ 235 final byte[] decodeChecked(CharSequence chars) 236 throws DecodingException { 237 chars = trimTrailingPadding(chars); 238 byte[] tmp = new byte[maxDecodedSize(chars.length())]; 239 int len = decodeTo(tmp, chars); 240 return extract(tmp, len); 241 } 242 243 /** 244 * Returns an {@code InputStream} that decodes base-encoded input from the specified {@code 245 * Reader}. The returned stream throws a {@link DecodingException} upon decoding-specific errors. 246 */ 247 @J2ktIncompatible 248 @GwtIncompatible // Reader,InputStream 249 public abstract InputStream decodingStream(Reader reader); 250 251 /** 252 * Returns a {@code ByteSource} that reads base-encoded bytes from the specified {@code 253 * CharSource}. 254 */ 255 @J2ktIncompatible 256 @GwtIncompatible // ByteSource,CharSource 257 public final ByteSource decodingSource(CharSource encodedSource) { 258 checkNotNull(encodedSource); 259 return new ByteSource() { 260 @Override 261 public InputStream openStream() throws IOException { 262 return decodingStream(encodedSource.openStream()); 263 } 264 }; 265 } 266 267 // Implementations for encoding/decoding 268 269 abstract int maxEncodedSize(int bytes); 270 271 abstract void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException; 272 273 abstract int maxDecodedSize(int chars); 274 275 abstract int decodeTo(byte[] target, CharSequence chars) throws DecodingException; 276 277 CharSequence trimTrailingPadding(CharSequence chars) { 278 return checkNotNull(chars); 279 } 280 281 // Modified encoding generators 282 283 /** 284 * Returns an encoding that behaves equivalently to this encoding, but omits any padding 285 * characters as specified by <a href="http://tools.ietf.org/html/rfc4648#section-3.2">RFC 4648 286 * section 3.2</a>, Padding of Encoded Data. 287 */ 288 public abstract BaseEncoding omitPadding(); 289 290 /** 291 * Returns an encoding that behaves equivalently to this encoding, but uses an alternate character 292 * for padding. 293 * 294 * @throws IllegalArgumentException if this padding character is already used in the alphabet or a 295 * separator 296 */ 297 public abstract BaseEncoding withPadChar(char padChar); 298 299 /** 300 * Returns an encoding that behaves equivalently to this encoding, but adds a separator string 301 * after every {@code n} characters. Any occurrences of any characters that occur in the separator 302 * are skipped over in decoding. 303 * 304 * @throws IllegalArgumentException if any alphabet or padding characters appear in the separator 305 * string, or if {@code n <= 0} 306 * @throws UnsupportedOperationException if this encoding already uses a separator 307 */ 308 public abstract BaseEncoding withSeparator(String separator, int n); 309 310 /** 311 * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with 312 * uppercase letters. Padding and separator characters remain in their original case. 313 * 314 * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and 315 * lower-case characters 316 */ 317 public abstract BaseEncoding upperCase(); 318 319 /** 320 * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with 321 * lowercase letters. Padding and separator characters remain in their original case. 322 * 323 * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and 324 * lower-case characters 325 */ 326 public abstract BaseEncoding lowerCase(); 327 328 /** 329 * Returns an encoding that behaves equivalently to this encoding, but decodes letters without 330 * regard to case. 331 * 332 * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and 333 * lower-case characters 334 * @since 32.0.0 335 */ 336 public abstract BaseEncoding ignoreCase(); 337 338 private static final BaseEncoding BASE64 = 339 new Base64Encoding( 340 "base64()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", '='); 341 342 /** 343 * The "base64" base encoding specified by <a 344 * href="http://tools.ietf.org/html/rfc4648#section-4">RFC 4648 section 4</a>, Base 64 Encoding. 345 * (This is the same as the base 64 encoding from <a 346 * href="http://tools.ietf.org/html/rfc3548#section-3">RFC 3548</a>.) 347 * 348 * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() 349 * omitted} or {@linkplain #withPadChar(char) replaced}. 350 * 351 * <p>No line feeds are added by default, as per <a 352 * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in 353 * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. 354 */ 355 public static BaseEncoding base64() { 356 return BASE64; 357 } 358 359 private static final BaseEncoding BASE64_URL = 360 new Base64Encoding( 361 "base64Url()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", '='); 362 363 /** 364 * The "base64url" encoding specified by <a 365 * href="http://tools.ietf.org/html/rfc4648#section-5">RFC 4648 section 5</a>, Base 64 Encoding 366 * with URL and Filename Safe Alphabet, also sometimes referred to as the "web safe Base64." (This 367 * is the same as the base 64 encoding with URL and filename safe alphabet from <a 368 * href="http://tools.ietf.org/html/rfc3548#section-4">RFC 3548</a>.) 369 * 370 * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() 371 * omitted} or {@linkplain #withPadChar(char) replaced}. 372 * 373 * <p>No line feeds are added by default, as per <a 374 * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in 375 * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. 376 */ 377 public static BaseEncoding base64Url() { 378 return BASE64_URL; 379 } 380 381 private static final BaseEncoding BASE32 = 382 new StandardBaseEncoding("base32()", "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", '='); 383 384 /** 385 * The "base32" encoding specified by <a href="http://tools.ietf.org/html/rfc4648#section-6">RFC 386 * 4648 section 6</a>, Base 32 Encoding. (This is the same as the base 32 encoding from <a 387 * href="http://tools.ietf.org/html/rfc3548#section-5">RFC 3548</a>.) 388 * 389 * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() 390 * omitted} or {@linkplain #withPadChar(char) replaced}. 391 * 392 * <p>No line feeds are added by default, as per <a 393 * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in 394 * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. 395 */ 396 public static BaseEncoding base32() { 397 return BASE32; 398 } 399 400 private static final BaseEncoding BASE32_HEX = 401 new StandardBaseEncoding("base32Hex()", "0123456789ABCDEFGHIJKLMNOPQRSTUV", '='); 402 403 /** 404 * The "base32hex" encoding specified by <a 405 * href="http://tools.ietf.org/html/rfc4648#section-7">RFC 4648 section 7</a>, Base 32 Encoding 406 * with Extended Hex Alphabet. There is no corresponding encoding in RFC 3548. 407 * 408 * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() 409 * omitted} or {@linkplain #withPadChar(char) replaced}. 410 * 411 * <p>No line feeds are added by default, as per <a 412 * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in 413 * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. 414 */ 415 public static BaseEncoding base32Hex() { 416 return BASE32_HEX; 417 } 418 419 private static final BaseEncoding BASE16 = new Base16Encoding("base16()", "0123456789ABCDEF"); 420 421 /** 422 * The "base16" encoding specified by <a href="http://tools.ietf.org/html/rfc4648#section-8">RFC 423 * 4648 section 8</a>, Base 16 Encoding. (This is the same as the base 16 encoding from <a 424 * href="http://tools.ietf.org/html/rfc3548#section-6">RFC 3548</a>.) This is commonly known as 425 * "hexadecimal" format. 426 * 427 * <p>No padding is necessary in base 16, so {@link #withPadChar(char)} and {@link #omitPadding()} 428 * have no effect. 429 * 430 * <p>No line feeds are added by default, as per <a 431 * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in 432 * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. 433 * 434 * <p><b>Java 17+ users:</b> Consider using the {@link java.util.HexFormat} API instead. 435 */ 436 public static BaseEncoding base16() { 437 return BASE16; 438 } 439 440 static final class Alphabet { 441 private final String name; 442 // this is meant to be immutable -- don't modify it! 443 private final char[] chars; 444 final int mask; 445 final int bitsPerChar; 446 final int charsPerChunk; 447 final int bytesPerChunk; 448 private final byte[] decodabet; 449 private final boolean[] validPadding; 450 private final boolean ignoreCase; 451 452 Alphabet(String name, char[] chars) { 453 this(name, chars, decodabetFor(chars), /* ignoreCase= */ false); 454 } 455 456 private Alphabet(String name, char[] chars, byte[] decodabet, boolean ignoreCase) { 457 this.name = checkNotNull(name); 458 this.chars = checkNotNull(chars); 459 try { 460 this.bitsPerChar = log2(chars.length, UNNECESSARY); 461 } catch (ArithmeticException e) { 462 throw new IllegalArgumentException("Illegal alphabet length " + chars.length, e); 463 } 464 465 // Compute how input bytes are chunked. For example, with base64 we chunk every 3 bytes into 466 // 4 characters. We have bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3. 467 // We're looking for the smallest charsPerChunk such that bitsPerChar * charsPerChunk is a 468 // multiple of 8. A multiple of 8 has 3 low zero bits, so we just need to figure out how many 469 // extra zero bits we need to add to the end of bitsPerChar to get 3 in total. 470 // The logic here would be wrong for bitsPerChar > 8, but since we require distinct ASCII 471 // characters that can't happen. 472 int zeroesInBitsPerChar = Integer.numberOfTrailingZeros(bitsPerChar); 473 this.charsPerChunk = 1 << (3 - zeroesInBitsPerChar); 474 this.bytesPerChunk = bitsPerChar >> zeroesInBitsPerChar; 475 476 this.mask = chars.length - 1; 477 478 this.decodabet = decodabet; 479 480 boolean[] validPadding = new boolean[charsPerChunk]; 481 for (int i = 0; i < bytesPerChunk; i++) { 482 validPadding[divide(i * 8, bitsPerChar, CEILING)] = true; 483 } 484 this.validPadding = validPadding; 485 this.ignoreCase = ignoreCase; 486 } 487 488 private static byte[] decodabetFor(char[] chars) { 489 byte[] decodabet = new byte[Ascii.MAX + 1]; 490 Arrays.fill(decodabet, (byte) -1); 491 for (int i = 0; i < chars.length; i++) { 492 char c = chars[i]; 493 checkArgument(c < decodabet.length, "Non-ASCII character: %s", c); 494 checkArgument(decodabet[c] == -1, "Duplicate character: %s", c); 495 decodabet[c] = (byte) i; 496 } 497 return decodabet; 498 } 499 500 /** Returns an equivalent {@code Alphabet} except it ignores case. */ 501 Alphabet ignoreCase() { 502 if (ignoreCase) { 503 return this; 504 } 505 506 // We can't use .clone() because of GWT. 507 byte[] newDecodabet = Arrays.copyOf(decodabet, decodabet.length); 508 for (int upper = 'A'; upper <= 'Z'; upper++) { 509 int lower = upper | 0x20; 510 byte decodeUpper = decodabet[upper]; 511 byte decodeLower = decodabet[lower]; 512 if (decodeUpper == -1) { 513 newDecodabet[upper] = decodeLower; 514 } else { 515 checkState( 516 decodeLower == -1, 517 "Can't ignoreCase() since '%s' and '%s' encode different values", 518 (char) upper, 519 (char) lower); 520 newDecodabet[lower] = decodeUpper; 521 } 522 } 523 return new Alphabet(name + ".ignoreCase()", chars, newDecodabet, /* ignoreCase= */ true); 524 } 525 526 char encode(int bits) { 527 return chars[bits]; 528 } 529 530 boolean isValidPaddingStartPosition(long index) { 531 return validPadding[(int) (index % charsPerChunk)]; 532 } 533 534 boolean canDecode(char ch) { 535 return ch <= Ascii.MAX && decodabet[ch] != -1; 536 } 537 538 int decode(char ch) throws DecodingException { 539 if (ch > Ascii.MAX) { 540 throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch)); 541 } 542 int result = decodabet[ch]; 543 if (result == -1) { 544 if (ch <= 0x20 || ch == Ascii.MAX) { 545 throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch)); 546 } else { 547 throw new DecodingException("Unrecognized character: " + ch); 548 } 549 } 550 return result; 551 } 552 553 private boolean hasLowerCase() { 554 for (char c : chars) { 555 if (Ascii.isLowerCase(c)) { 556 return true; 557 } 558 } 559 return false; 560 } 561 562 private boolean hasUpperCase() { 563 for (char c : chars) { 564 if (Ascii.isUpperCase(c)) { 565 return true; 566 } 567 } 568 return false; 569 } 570 571 Alphabet upperCase() { 572 if (!hasLowerCase()) { 573 return this; 574 } 575 checkState(!hasUpperCase(), "Cannot call upperCase() on a mixed-case alphabet"); 576 char[] upperCased = new char[chars.length]; 577 for (int i = 0; i < chars.length; i++) { 578 upperCased[i] = Ascii.toUpperCase(chars[i]); 579 } 580 Alphabet upperCase = new Alphabet(name + ".upperCase()", upperCased); 581 return ignoreCase ? upperCase.ignoreCase() : upperCase; 582 } 583 584 Alphabet lowerCase() { 585 if (!hasUpperCase()) { 586 return this; 587 } 588 checkState(!hasLowerCase(), "Cannot call lowerCase() on a mixed-case alphabet"); 589 char[] lowerCased = new char[chars.length]; 590 for (int i = 0; i < chars.length; i++) { 591 lowerCased[i] = Ascii.toLowerCase(chars[i]); 592 } 593 Alphabet lowerCase = new Alphabet(name + ".lowerCase()", lowerCased); 594 return ignoreCase ? lowerCase.ignoreCase() : lowerCase; 595 } 596 597 public boolean matches(char c) { 598 return c < decodabet.length && decodabet[c] != -1; 599 } 600 601 @Override 602 public String toString() { 603 return name; 604 } 605 606 @Override 607 public boolean equals(@Nullable Object other) { 608 if (other instanceof Alphabet) { 609 Alphabet that = (Alphabet) other; 610 return this.ignoreCase == that.ignoreCase && Arrays.equals(this.chars, that.chars); 611 } 612 return false; 613 } 614 615 @Override 616 public int hashCode() { 617 return Arrays.hashCode(chars) + (ignoreCase ? 1231 : 1237); 618 } 619 } 620 621 private static class StandardBaseEncoding extends BaseEncoding { 622 final Alphabet alphabet; 623 624 final @Nullable Character paddingChar; 625 626 StandardBaseEncoding(String name, String alphabetChars, @Nullable Character paddingChar) { 627 this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); 628 } 629 630 StandardBaseEncoding(Alphabet alphabet, @Nullable Character paddingChar) { 631 this.alphabet = checkNotNull(alphabet); 632 checkArgument( 633 paddingChar == null || !alphabet.matches(paddingChar), 634 "Padding character %s was already in alphabet", 635 paddingChar); 636 this.paddingChar = paddingChar; 637 } 638 639 @Override 640 int maxEncodedSize(int bytes) { 641 return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk, CEILING); 642 } 643 644 @J2ktIncompatible 645 @GwtIncompatible // Writer,OutputStream 646 @Override 647 public OutputStream encodingStream(Writer out) { 648 checkNotNull(out); 649 return new OutputStream() { 650 @GuardedBy("this") 651 int bitBuffer = 0; 652 653 @GuardedBy("this") 654 int bitBufferLength = 0; 655 656 @GuardedBy("this") 657 int writtenChars = 0; 658 659 @GuardedBy("this") 660 boolean closed = false; 661 662 @Override 663 public synchronized void write(byte[] b, int off, int len) throws IOException { 664 super.write(b, off, len); 665 } 666 667 @Override 668 public synchronized void write(int b) throws IOException { 669 checkOpen(); 670 bitBuffer <<= 8; 671 bitBuffer |= b & 0xFF; 672 bitBufferLength += 8; 673 while (bitBufferLength >= alphabet.bitsPerChar) { 674 int charIndex = (bitBuffer >> (bitBufferLength - alphabet.bitsPerChar)) & alphabet.mask; 675 out.write(alphabet.encode(charIndex)); 676 writtenChars++; 677 bitBufferLength -= alphabet.bitsPerChar; 678 } 679 } 680 681 @Override 682 public synchronized void flush() throws IOException { 683 checkOpen(); 684 out.flush(); 685 } 686 687 @GuardedBy("this") 688 void checkOpen() throws IOException { 689 if (closed) { 690 throw new IOException("stream closed"); 691 } 692 } 693 694 @Override 695 public synchronized void close() throws IOException { 696 if (closed) { 697 return; 698 } 699 closed = true; 700 if (bitBufferLength > 0) { 701 int charIndex = (bitBuffer << (alphabet.bitsPerChar - bitBufferLength)) & alphabet.mask; 702 out.write(alphabet.encode(charIndex)); 703 writtenChars++; 704 if (paddingChar != null) { 705 while (writtenChars % alphabet.charsPerChunk != 0) { 706 out.write(paddingChar); 707 writtenChars++; 708 } 709 } 710 } 711 out.close(); 712 } 713 }; 714 } 715 716 @Override 717 void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { 718 checkNotNull(target); 719 checkPositionIndexes(off, off + len, bytes.length); 720 for (int i = 0; i < len; i += alphabet.bytesPerChunk) { 721 encodeChunkTo(target, bytes, off + i, min(alphabet.bytesPerChunk, len - i)); 722 } 723 } 724 725 final void encodeChunkTo(Appendable target, byte[] bytes, int off, int len) throws IOException { 726 checkNotNull(target); 727 checkPositionIndexes(off, off + len, bytes.length); 728 checkArgument(len <= alphabet.bytesPerChunk); 729 long bitBuffer = 0; 730 for (int i = 0; i < len; ++i) { 731 bitBuffer |= bytes[off + i] & 0xFF; 732 bitBuffer <<= 8; // Add additional zero byte in the end. 733 } 734 // Position of first character is length of bitBuffer minus bitsPerChar. 735 int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar; 736 int bitsProcessed = 0; 737 while (bitsProcessed < len * 8) { 738 int charIndex = (int) (bitBuffer >>> (bitOffset - bitsProcessed)) & alphabet.mask; 739 target.append(alphabet.encode(charIndex)); 740 bitsProcessed += alphabet.bitsPerChar; 741 } 742 if (paddingChar != null) { 743 while (bitsProcessed < alphabet.bytesPerChunk * 8) { 744 target.append(paddingChar); 745 bitsProcessed += alphabet.bitsPerChar; 746 } 747 } 748 } 749 750 @Override 751 int maxDecodedSize(int chars) { 752 return (int) ((alphabet.bitsPerChar * (long) chars + 7L) / 8L); 753 } 754 755 @Override 756 final CharSequence trimTrailingPadding(CharSequence chars) { 757 checkNotNull(chars); 758 if (paddingChar == null) { 759 return chars; 760 } 761 char padChar = paddingChar; 762 int l; 763 for (l = chars.length() - 1; l >= 0; l--) { 764 if (chars.charAt(l) != padChar) { 765 break; 766 } 767 } 768 return chars.subSequence(0, l + 1); 769 } 770 771 @Override 772 public boolean canDecode(CharSequence chars) { 773 checkNotNull(chars); 774 chars = trimTrailingPadding(chars); 775 if (!alphabet.isValidPaddingStartPosition(chars.length())) { 776 return false; 777 } 778 for (int i = 0; i < chars.length(); i++) { 779 if (!alphabet.canDecode(chars.charAt(i))) { 780 return false; 781 } 782 } 783 return true; 784 } 785 786 @Override 787 int decodeTo(byte[] target, CharSequence chars) throws DecodingException { 788 checkNotNull(target); 789 chars = trimTrailingPadding(chars); 790 if (!alphabet.isValidPaddingStartPosition(chars.length())) { 791 throw new DecodingException("Invalid input length " + chars.length()); 792 } 793 int bytesWritten = 0; 794 for (int charIdx = 0; charIdx < chars.length(); charIdx += alphabet.charsPerChunk) { 795 long chunk = 0; 796 int charsProcessed = 0; 797 for (int i = 0; i < alphabet.charsPerChunk; i++) { 798 chunk <<= alphabet.bitsPerChar; 799 if (charIdx + i < chars.length()) { 800 chunk |= alphabet.decode(chars.charAt(charIdx + charsProcessed++)); 801 } 802 } 803 int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar; 804 for (int offset = (alphabet.bytesPerChunk - 1) * 8; offset >= minOffset; offset -= 8) { 805 target[bytesWritten++] = (byte) ((chunk >>> offset) & 0xFF); 806 } 807 } 808 return bytesWritten; 809 } 810 811 @Override 812 @J2ktIncompatible 813 @GwtIncompatible // Reader,InputStream 814 public InputStream decodingStream(Reader reader) { 815 checkNotNull(reader); 816 return new InputStream() { 817 @GuardedBy("this") 818 int bitBuffer = 0; 819 820 @GuardedBy("this") 821 int bitBufferLength = 0; 822 823 @GuardedBy("this") 824 long readChars = 0; 825 826 @GuardedBy("this") 827 boolean hitPadding = false; 828 829 @Override 830 public synchronized int read() throws IOException { 831 while (true) { 832 int readChar = reader.read(); 833 if (readChar == -1) { 834 if (!hitPadding && !alphabet.isValidPaddingStartPosition(readChars)) { 835 throw new DecodingException("Invalid input length " + readChars); 836 } 837 return -1; 838 } 839 readChars++; 840 char ch = (char) readChar; 841 if (paddingChar != null && paddingChar == ch) { 842 if (!hitPadding 843 && (readChars == 1 || !alphabet.isValidPaddingStartPosition(readChars - 1))) { 844 throw new DecodingException("Padding cannot start at index " + readChars); 845 } 846 hitPadding = true; 847 } else if (hitPadding) { 848 throw new DecodingException( 849 "Expected padding character but found '" + ch + "' at index " + readChars); 850 } else { 851 bitBuffer <<= alphabet.bitsPerChar; 852 bitBuffer |= alphabet.decode(ch); 853 bitBufferLength += alphabet.bitsPerChar; 854 855 if (bitBufferLength >= 8) { 856 bitBufferLength -= 8; 857 return (bitBuffer >> bitBufferLength) & 0xFF; 858 } 859 } 860 } 861 } 862 863 @Override 864 public synchronized int read(byte[] buf, int off, int len) throws IOException { 865 // Overriding this to work around the fact that InputStream's default implementation of 866 // this method will silently swallow exceptions thrown by the single-byte read() method 867 // (other than on the first call to it), which in this case can cause invalid encoded 868 // strings to not throw an exception. 869 // See https://github.com/google/guava/issues/3542 870 checkPositionIndexes(off, off + len, buf.length); 871 872 int i = off; 873 for (; i < off + len; i++) { 874 int b = read(); 875 if (b == -1) { 876 int read = i - off; 877 return read == 0 ? -1 : read; 878 } 879 buf[i] = (byte) b; 880 } 881 return i - off; 882 } 883 884 @Override 885 public synchronized void close() throws IOException { 886 reader.close(); 887 } 888 }; 889 } 890 891 @Override 892 public BaseEncoding omitPadding() { 893 return (paddingChar == null) ? this : newInstance(alphabet, null); 894 } 895 896 @Override 897 public BaseEncoding withPadChar(char padChar) { 898 if (8 % alphabet.bitsPerChar == 0 || (paddingChar != null && paddingChar == padChar)) { 899 return this; 900 } else { 901 return newInstance(alphabet, padChar); 902 } 903 } 904 905 @Override 906 public BaseEncoding withSeparator(String separator, int afterEveryChars) { 907 for (int i = 0; i < separator.length(); i++) { 908 checkArgument( 909 !alphabet.matches(separator.charAt(i)), 910 "Separator (%s) cannot contain alphabet characters", 911 separator); 912 } 913 if (paddingChar != null) { 914 checkArgument( 915 separator.indexOf(paddingChar) < 0, 916 "Separator (%s) cannot contain padding character", 917 separator); 918 } 919 return new SeparatedBaseEncoding(this, separator, afterEveryChars); 920 } 921 922 @LazyInit private volatile @Nullable BaseEncoding upperCase; 923 @LazyInit private volatile @Nullable BaseEncoding lowerCase; 924 @LazyInit private volatile @Nullable BaseEncoding ignoreCase; 925 926 @Override 927 public BaseEncoding upperCase() { 928 BaseEncoding result = upperCase; 929 if (result == null) { 930 Alphabet upper = alphabet.upperCase(); 931 result = upperCase = (upper == alphabet) ? this : newInstance(upper, paddingChar); 932 } 933 return result; 934 } 935 936 @Override 937 public BaseEncoding lowerCase() { 938 BaseEncoding result = lowerCase; 939 if (result == null) { 940 Alphabet lower = alphabet.lowerCase(); 941 result = lowerCase = (lower == alphabet) ? this : newInstance(lower, paddingChar); 942 } 943 return result; 944 } 945 946 @Override 947 public BaseEncoding ignoreCase() { 948 BaseEncoding result = ignoreCase; 949 if (result == null) { 950 Alphabet ignore = alphabet.ignoreCase(); 951 result = ignoreCase = (ignore == alphabet) ? this : newInstance(ignore, paddingChar); 952 } 953 return result; 954 } 955 956 BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { 957 return new StandardBaseEncoding(alphabet, paddingChar); 958 } 959 960 @Override 961 public final String toString() { 962 StringBuilder builder = new StringBuilder("BaseEncoding."); 963 builder.append(alphabet); 964 if (8 % alphabet.bitsPerChar != 0) { 965 if (paddingChar == null) { 966 builder.append(".omitPadding()"); 967 } else { 968 builder.append(".withPadChar('").append(paddingChar).append("')"); 969 } 970 } 971 return builder.toString(); 972 } 973 974 @Override 975 public final boolean equals(@Nullable Object other) { 976 if (other instanceof StandardBaseEncoding) { 977 StandardBaseEncoding that = (StandardBaseEncoding) other; 978 return this.alphabet.equals(that.alphabet) 979 && Objects.equals(this.paddingChar, that.paddingChar); 980 } 981 return false; 982 } 983 984 @Override 985 public final int hashCode() { 986 return alphabet.hashCode() ^ Objects.hashCode(paddingChar); 987 } 988 } 989 990 private static final class Base16Encoding extends StandardBaseEncoding { 991 final char[] encoding = new char[512]; 992 993 Base16Encoding(String name, String alphabetChars) { 994 this(new Alphabet(name, alphabetChars.toCharArray())); 995 } 996 997 private Base16Encoding(Alphabet alphabet) { 998 super(alphabet, null); 999 checkArgument(alphabet.chars.length == 16); 1000 for (int i = 0; i < 256; ++i) { 1001 encoding[i] = alphabet.encode(i >>> 4); 1002 encoding[i | 0x100] = alphabet.encode(i & 0xF); 1003 } 1004 } 1005 1006 @Override 1007 void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { 1008 checkNotNull(target); 1009 checkPositionIndexes(off, off + len, bytes.length); 1010 for (int i = 0; i < len; ++i) { 1011 int b = bytes[off + i] & 0xFF; 1012 target.append(encoding[b]); 1013 target.append(encoding[b | 0x100]); 1014 } 1015 } 1016 1017 @Override 1018 int decodeTo(byte[] target, CharSequence chars) throws DecodingException { 1019 checkNotNull(target); 1020 if (chars.length() % 2 == 1) { 1021 throw new DecodingException("Invalid input length " + chars.length()); 1022 } 1023 int bytesWritten = 0; 1024 for (int i = 0; i < chars.length(); i += 2) { 1025 int decoded = alphabet.decode(chars.charAt(i)) << 4 | alphabet.decode(chars.charAt(i + 1)); 1026 target[bytesWritten++] = (byte) decoded; 1027 } 1028 return bytesWritten; 1029 } 1030 1031 @Override 1032 BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { 1033 return new Base16Encoding(alphabet); 1034 } 1035 } 1036 1037 private static final class Base64Encoding extends StandardBaseEncoding { 1038 Base64Encoding(String name, String alphabetChars, @Nullable Character paddingChar) { 1039 this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); 1040 } 1041 1042 private Base64Encoding(Alphabet alphabet, @Nullable Character paddingChar) { 1043 super(alphabet, paddingChar); 1044 checkArgument(alphabet.chars.length == 64); 1045 } 1046 1047 @Override 1048 void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { 1049 checkNotNull(target); 1050 checkPositionIndexes(off, off + len, bytes.length); 1051 int i = off; 1052 for (int remaining = len; remaining >= 3; remaining -= 3) { 1053 int chunk = (bytes[i++] & 0xFF) << 16 | (bytes[i++] & 0xFF) << 8 | (bytes[i++] & 0xFF); 1054 target.append(alphabet.encode(chunk >>> 18)); 1055 target.append(alphabet.encode((chunk >>> 12) & 0x3F)); 1056 target.append(alphabet.encode((chunk >>> 6) & 0x3F)); 1057 target.append(alphabet.encode(chunk & 0x3F)); 1058 } 1059 if (i < off + len) { 1060 encodeChunkTo(target, bytes, i, off + len - i); 1061 } 1062 } 1063 1064 @Override 1065 int decodeTo(byte[] target, CharSequence chars) throws DecodingException { 1066 checkNotNull(target); 1067 chars = trimTrailingPadding(chars); 1068 if (!alphabet.isValidPaddingStartPosition(chars.length())) { 1069 throw new DecodingException("Invalid input length " + chars.length()); 1070 } 1071 int bytesWritten = 0; 1072 for (int i = 0; i < chars.length(); ) { 1073 int chunk = alphabet.decode(chars.charAt(i++)) << 18; 1074 chunk |= alphabet.decode(chars.charAt(i++)) << 12; 1075 target[bytesWritten++] = (byte) (chunk >>> 16); 1076 if (i < chars.length()) { 1077 chunk |= alphabet.decode(chars.charAt(i++)) << 6; 1078 target[bytesWritten++] = (byte) ((chunk >>> 8) & 0xFF); 1079 if (i < chars.length()) { 1080 chunk |= alphabet.decode(chars.charAt(i++)); 1081 target[bytesWritten++] = (byte) (chunk & 0xFF); 1082 } 1083 } 1084 } 1085 return bytesWritten; 1086 } 1087 1088 @Override 1089 BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) { 1090 return new Base64Encoding(alphabet, paddingChar); 1091 } 1092 } 1093 1094 @J2ktIncompatible 1095 @GwtIncompatible 1096 static Reader ignoringReader(Reader delegate, String toIgnore) { 1097 checkNotNull(delegate); 1098 checkNotNull(toIgnore); 1099 return new Reader() { 1100 @Override 1101 public int read() throws IOException { 1102 int readChar; 1103 do { 1104 readChar = delegate.read(); 1105 } while (readChar != -1 && toIgnore.indexOf((char) readChar) >= 0); 1106 return readChar; 1107 } 1108 1109 @Override 1110 public int read(char[] cbuf, int off, int len) { 1111 throw new UnsupportedOperationException(); 1112 } 1113 1114 @Override 1115 public void close() throws IOException { 1116 delegate.close(); 1117 } 1118 }; 1119 } 1120 1121 static Appendable separatingAppendable( 1122 Appendable delegate, String separator, int afterEveryChars) { 1123 checkNotNull(delegate); 1124 checkNotNull(separator); 1125 checkArgument(afterEveryChars > 0); 1126 return new Appendable() { 1127 int charsUntilSeparator = afterEveryChars; 1128 1129 @Override 1130 public Appendable append(char c) throws IOException { 1131 if (charsUntilSeparator == 0) { 1132 delegate.append(separator); 1133 charsUntilSeparator = afterEveryChars; 1134 } 1135 delegate.append(c); 1136 charsUntilSeparator--; 1137 return this; 1138 } 1139 1140 @Override 1141 public Appendable append(@Nullable CharSequence chars, int off, int len) { 1142 throw new UnsupportedOperationException(); 1143 } 1144 1145 @Override 1146 public Appendable append(@Nullable CharSequence chars) { 1147 throw new UnsupportedOperationException(); 1148 } 1149 }; 1150 } 1151 1152 @J2ktIncompatible 1153 @GwtIncompatible // Writer 1154 static Writer separatingWriter(Writer delegate, String separator, int afterEveryChars) { 1155 Appendable separatingAppendable = separatingAppendable(delegate, separator, afterEveryChars); 1156 return new Writer() { 1157 @Override 1158 public void write(int c) throws IOException { 1159 separatingAppendable.append((char) c); 1160 } 1161 1162 @Override 1163 public void write(char[] chars, int off, int len) { 1164 throw new UnsupportedOperationException(); 1165 } 1166 1167 @Override 1168 public void flush() throws IOException { 1169 delegate.flush(); 1170 } 1171 1172 @Override 1173 public void close() throws IOException { 1174 delegate.close(); 1175 } 1176 }; 1177 } 1178 1179 static final class SeparatedBaseEncoding extends BaseEncoding { 1180 private final BaseEncoding delegate; 1181 private final String separator; 1182 private final int afterEveryChars; 1183 1184 SeparatedBaseEncoding(BaseEncoding delegate, String separator, int afterEveryChars) { 1185 this.delegate = checkNotNull(delegate); 1186 this.separator = checkNotNull(separator); 1187 this.afterEveryChars = afterEveryChars; 1188 checkArgument( 1189 afterEveryChars > 0, "Cannot add a separator after every %s chars", afterEveryChars); 1190 } 1191 1192 @Override 1193 CharSequence trimTrailingPadding(CharSequence chars) { 1194 return delegate.trimTrailingPadding(chars); 1195 } 1196 1197 @Override 1198 int maxEncodedSize(int bytes) { 1199 int unseparatedSize = delegate.maxEncodedSize(bytes); 1200 return unseparatedSize 1201 + separator.length() * divide(max(0, unseparatedSize - 1), afterEveryChars, FLOOR); 1202 } 1203 1204 @J2ktIncompatible 1205 @GwtIncompatible // Writer,OutputStream 1206 @Override 1207 public OutputStream encodingStream(Writer output) { 1208 return delegate.encodingStream(separatingWriter(output, separator, afterEveryChars)); 1209 } 1210 1211 @Override 1212 void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { 1213 delegate.encodeTo(separatingAppendable(target, separator, afterEveryChars), bytes, off, len); 1214 } 1215 1216 @Override 1217 int maxDecodedSize(int chars) { 1218 return delegate.maxDecodedSize(chars); 1219 } 1220 1221 @Override 1222 public boolean canDecode(CharSequence chars) { 1223 StringBuilder builder = new StringBuilder(); 1224 for (int i = 0; i < chars.length(); i++) { 1225 char c = chars.charAt(i); 1226 if (separator.indexOf(c) < 0) { 1227 builder.append(c); 1228 } 1229 } 1230 return delegate.canDecode(builder); 1231 } 1232 1233 @Override 1234 int decodeTo(byte[] target, CharSequence chars) throws DecodingException { 1235 StringBuilder stripped = new StringBuilder(chars.length()); 1236 for (int i = 0; i < chars.length(); i++) { 1237 char c = chars.charAt(i); 1238 if (separator.indexOf(c) < 0) { 1239 stripped.append(c); 1240 } 1241 } 1242 return delegate.decodeTo(target, stripped); 1243 } 1244 1245 @Override 1246 @J2ktIncompatible 1247 @GwtIncompatible // Reader,InputStream 1248 public InputStream decodingStream(Reader reader) { 1249 return delegate.decodingStream(ignoringReader(reader, separator)); 1250 } 1251 1252 @Override 1253 public BaseEncoding omitPadding() { 1254 return delegate.omitPadding().withSeparator(separator, afterEveryChars); 1255 } 1256 1257 @Override 1258 public BaseEncoding withPadChar(char padChar) { 1259 return delegate.withPadChar(padChar).withSeparator(separator, afterEveryChars); 1260 } 1261 1262 @Override 1263 public BaseEncoding withSeparator(String separator, int afterEveryChars) { 1264 throw new UnsupportedOperationException("Already have a separator"); 1265 } 1266 1267 @Override 1268 public BaseEncoding upperCase() { 1269 return delegate.upperCase().withSeparator(separator, afterEveryChars); 1270 } 1271 1272 @Override 1273 public BaseEncoding lowerCase() { 1274 return delegate.lowerCase().withSeparator(separator, afterEveryChars); 1275 } 1276 1277 @Override 1278 public BaseEncoding ignoreCase() { 1279 return delegate.ignoreCase().withSeparator(separator, afterEveryChars); 1280 } 1281 1282 @Override 1283 public String toString() { 1284 return delegate + ".withSeparator(\"" + separator + "\", " + afterEveryChars + ")"; 1285 } 1286 } 1287}