// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package com.firefly.utils.codec; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; /** * Fast B64 Encoder/Decoder as described in RFC 1421. * <p>Does not insert or interpret whitespace as described in RFC * 1521. If you require this you must pre/post process your data. * <p> Note that in a web context the usual case is to not want * linebreaks or other white space in the encoded output. */ public class B64Code { private static final char __pad = '='; private static final char[] __rfc1421alphabet = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; private static final byte[] __rfc1421nibbles; static { __rfc1421nibbles = new byte[256]; for (int i = 0; i < 256; i++) __rfc1421nibbles[i] = -1; for (byte b = 0; b < 64; b++) __rfc1421nibbles[(byte) __rfc1421alphabet[b]] = b; __rfc1421nibbles[(byte) __pad] = 0; } private static final char[] __rfc4648urlAlphabet = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; private static final byte[] __rfc4648urlNibbles; static { __rfc4648urlNibbles = new byte[256]; for (int i = 0; i < 256; i++) __rfc4648urlNibbles[i] = -1; for (byte b = 0; b < 64; b++) __rfc4648urlNibbles[(byte) __rfc4648urlAlphabet[b]] = b; __rfc4648urlNibbles[(byte) __pad] = 0; } private B64Code() { } /** * Base 64 encode as described in RFC 1421. * <p>Does not insert whitespace as described in RFC 1521. * * @param s String to encode. * @return String containing the encoded form of the input. */ public static String encode(String s) { return encode(s, (Charset) null); } /** * Base 64 encode as described in RFC 1421. * <p>Does not insert whitespace as described in RFC 1521. * * @param s String to encode. * @param charEncoding String representing the name of * the character encoding of the provided input String. * @return String containing the encoded form of the input. */ public static String encode(String s, String charEncoding) { byte[] bytes; if (charEncoding == null) bytes = s.getBytes(StandardCharsets.ISO_8859_1); else bytes = s.getBytes(Charset.forName(charEncoding)); return new String(encode(bytes)); } /** * Base 64 encode as described in RFC 1421. * <p>Does not insert whitespace as described in RFC 1521. * * @param s String to encode. * @param charEncoding The character encoding of the provided input String. * @return String containing the encoded form of the input. */ public static String encode(String s, Charset charEncoding) { byte[] bytes = s.getBytes(charEncoding == null ? StandardCharsets.ISO_8859_1 : charEncoding); return new String(encode(bytes)); } /** * Fast Base 64 encode as described in RFC 1421. * <p>Does not insert whitespace as described in RFC 1521. * <p> Avoids creating extra copies of the input/output. * * @param b byte array to encode. * @return char array containing the encoded form of the input. */ public static char[] encode(byte[] b) { if (b == null) return null; int bLen = b.length; int cLen = ((bLen + 2) / 3) * 4; char c[] = new char[cLen]; int ci = 0; int bi = 0; byte b0, b1, b2; int stop = (bLen / 3) * 3; while (bi < stop) { b0 = b[bi++]; b1 = b[bi++]; b2 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f]; c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f | (b2 >>> 6) & 0x03]; c[ci++] = __rfc1421alphabet[b2 & 0x3f]; } if (bLen != bi) { switch (bLen % 3) { case 2: b0 = b[bi++]; b1 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f]; c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f]; c[ci++] = __pad; break; case 1: b0 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f]; c[ci++] = __pad; c[ci++] = __pad; break; default: break; } } return c; } /** * Fast Base 64 encode as described in RFC 1421 and RFC2045 * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true. * <p> Avoids creating extra copies of the input/output. * * @param b byte array to encode. * @param rfc2045 If true, break lines at 76 characters with CRLF * @return char array containing the encoded form of the input. */ public static char[] encode(byte[] b, boolean rfc2045) { if (b == null) return null; if (!rfc2045) return encode(b); int bLen = b.length; int cLen = ((bLen + 2) / 3) * 4; cLen += 2 + 2 * (cLen / 76); char c[] = new char[cLen]; int ci = 0; int bi = 0; byte b0, b1, b2; int stop = (bLen / 3) * 3; int l = 0; while (bi < stop) { b0 = b[bi++]; b1 = b[bi++]; b2 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f]; c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f | (b2 >>> 6) & 0x03]; c[ci++] = __rfc1421alphabet[b2 & 0x3f]; l += 4; if (l % 76 == 0) { c[ci++] = 13; c[ci++] = 10; } } if (bLen != bi) { switch (bLen % 3) { case 2: b0 = b[bi++]; b1 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f]; c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f]; c[ci++] = __pad; break; case 1: b0 = b[bi++]; c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f]; c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f]; c[ci++] = __pad; c[ci++] = __pad; break; default: break; } } c[ci++] = 13; c[ci++] = 10; return c; } /** * Base 64 decode as described in RFC 2045. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * * @param encoded String to decode. * @param charEncoding String representing the character encoding * used to map the decoded bytes into a String. * @return String decoded byte array. * @throws UnsupportedCharsetException if the encoding is not supported * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ public static String decode(String encoded, String charEncoding) { byte[] decoded = decode(encoded); if (charEncoding == null) return new String(decoded); return new String(decoded, Charset.forName(charEncoding)); } /** * Base 64 decode as described in RFC 2045. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * * @param encoded String to decode. * @param charEncoding Character encoding * used to map the decoded bytes into a String. * @return String decoded byte array. * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ public static String decode(String encoded, Charset charEncoding) { byte[] decoded = decode(encoded); if (charEncoding == null) return new String(decoded); return new String(decoded, charEncoding); } /** * Fast Base 64 decode as described in RFC 1421. * <p> * <p>Unlike other decode methods, this does not attempt to * cope with extra whitespace as described in RFC 1521/2045. * <p> Avoids creating extra copies of the input/output. * <p> Note this code has been flattened for performance. * * @param b char array to decode. * @return byte array containing the decoded form of the input. * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ public static byte[] decode(char[] b) { if (b == null) return null; int bLen = b.length; if (bLen % 4 != 0) throw new IllegalArgumentException("Input block size is not 4"); int li = bLen - 1; while (li >= 0 && b[li] == (byte) __pad) li--; if (li < 0) return new byte[0]; // Create result array of exact required size. int rLen = ((li + 1) * 3) / 4; byte r[] = new byte[rLen]; int ri = 0; int bi = 0; int stop = (rLen / 3) * 3; byte b0, b1, b2, b3; try { while (ri < stop) { b0 = __rfc1421nibbles[b[bi++]]; b1 = __rfc1421nibbles[b[bi++]]; b2 = __rfc1421nibbles[b[bi++]]; b3 = __rfc1421nibbles[b[bi++]]; if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) throw new IllegalArgumentException("Not B64 encoded"); r[ri++] = (byte) (b0 << 2 | b1 >>> 4); r[ri++] = (byte) (b1 << 4 | b2 >>> 2); r[ri++] = (byte) (b2 << 6 | b3); } if (rLen != ri) { switch (rLen % 3) { case 2: b0 = __rfc1421nibbles[b[bi++]]; b1 = __rfc1421nibbles[b[bi++]]; b2 = __rfc1421nibbles[b[bi++]]; if (b0 < 0 || b1 < 0 || b2 < 0) throw new IllegalArgumentException("Not B64 encoded"); r[ri++] = (byte) (b0 << 2 | b1 >>> 4); r[ri++] = (byte) (b1 << 4 | b2 >>> 2); break; case 1: b0 = __rfc1421nibbles[b[bi++]]; b1 = __rfc1421nibbles[b[bi++]]; if (b0 < 0 || b1 < 0) throw new IllegalArgumentException("Not B64 encoded"); r[ri++] = (byte) (b0 << 2 | b1 >>> 4); break; default: break; } } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException("char " + bi + " was not B64 encoded"); } return r; } /** * Base 64 decode as described in RFC 2045. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * * @param encoded String to decode. * @return byte array containing the decoded form of the input. * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ public static byte[] decode(String encoded) { if (encoded == null) return null; ByteArrayOutputStream bout = new ByteArrayOutputStream(4 * encoded.length() / 3); decode(encoded, bout); return bout.toByteArray(); } /* ------------------------------------------------------------ */ /** * Base 64 decode as described in RFC 2045. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * * @param encoded String to decode. * @param bout stream for decoded bytes * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ static public void decode(String encoded, ByteArrayOutputStream bout) { if (encoded == null) return; if (bout == null) throw new IllegalArgumentException("No outputstream for decoded bytes"); int ci = 0; byte nibbles[] = new byte[4]; int s = 0; while (ci < encoded.length()) { char c = encoded.charAt(ci++); if (c == __pad) break; if (Character.isWhitespace(c)) continue; byte nibble = __rfc1421nibbles[c]; if (nibble < 0) throw new IllegalArgumentException("Not B64 encoded"); nibbles[s++] = __rfc1421nibbles[c]; switch (s) { case 1: break; case 2: bout.write(nibbles[0] << 2 | nibbles[1] >>> 4); break; case 3: bout.write(nibbles[1] << 4 | nibbles[2] >>> 2); break; case 4: bout.write(nibbles[2] << 6 | nibbles[3]); s = 0; break; } } } /* ------------------------------------------------------------ */ public static byte[] decodeRFC4648URL(String encoded) { if (encoded == null) return null; ByteArrayOutputStream bout = new ByteArrayOutputStream(4 * encoded.length() / 3); decodeRFC4648URL(encoded, bout); return bout.toByteArray(); } /* ------------------------------------------------------------ */ /** * Base 64 decode as described in RFC 4648 URL. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * * @param encoded String to decode. * @param bout stream for decoded bytes * @throws IllegalArgumentException if the input is not a valid * B64 encoding. */ static public void decodeRFC4648URL(String encoded, ByteArrayOutputStream bout) { if (encoded == null) return; if (bout == null) throw new IllegalArgumentException("No outputstream for decoded bytes"); int ci = 0; byte nibbles[] = new byte[4]; int s = 0; while (ci < encoded.length()) { char c = encoded.charAt(ci++); if (c == __pad) break; if (Character.isWhitespace(c)) continue; byte nibble = __rfc4648urlNibbles[c]; if (nibble < 0) throw new IllegalArgumentException("Not B64 encoded"); nibbles[s++] = __rfc4648urlNibbles[c]; switch (s) { case 1: break; case 2: bout.write(nibbles[0] << 2 | nibbles[1] >>> 4); break; case 3: bout.write(nibbles[1] << 4 | nibbles[2] >>> 2); break; case 4: bout.write(nibbles[2] << 6 | nibbles[3]); s = 0; break; } } } public static void encode(int value, Appendable buf) throws IOException { buf.append(__rfc1421alphabet[0x3f & ((0xFC000000 & value) >> 26)]); buf.append(__rfc1421alphabet[0x3f & ((0x03F00000 & value) >> 20)]); buf.append(__rfc1421alphabet[0x3f & ((0x000FC000 & value) >> 14)]); buf.append(__rfc1421alphabet[0x3f & ((0x00003F00 & value) >> 8)]); buf.append(__rfc1421alphabet[0x3f & ((0x000000FC & value) >> 2)]); buf.append(__rfc1421alphabet[0x3f & ((0x00000003 & value) << 4)]); buf.append('='); } public static void encode(long lvalue, Appendable buf) throws IOException { int value = (int) (0xFFFFFFFC & (lvalue >> 32)); buf.append(__rfc1421alphabet[0x3f & ((0xFC000000 & value) >> 26)]); buf.append(__rfc1421alphabet[0x3f & ((0x03F00000 & value) >> 20)]); buf.append(__rfc1421alphabet[0x3f & ((0x000FC000 & value) >> 14)]); buf.append(__rfc1421alphabet[0x3f & ((0x00003F00 & value) >> 8)]); buf.append(__rfc1421alphabet[0x3f & ((0x000000FC & value) >> 2)]); buf.append(__rfc1421alphabet[0x3f & ((0x00000003 & value) << 4) + (0xf & (int) (lvalue >> 28))]); value = 0x0FFFFFFF & (int) lvalue; buf.append(__rfc1421alphabet[0x3f & ((0x0FC00000 & value) >> 22)]); buf.append(__rfc1421alphabet[0x3f & ((0x003F0000 & value) >> 16)]); buf.append(__rfc1421alphabet[0x3f & ((0x0000FC00 & value) >> 10)]); buf.append(__rfc1421alphabet[0x3f & ((0x000003F0 & value) >> 4)]); buf.append(__rfc1421alphabet[0x3f & ((0x0000000F & value) << 2)]); } }