2017-05-19 09:56:24 +08:00

492 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package org.csource.common;
import java.io.IOException;
/**
* Freeware from:
* Roedy Green
* Canadian Mind Products
* #327 - 964 Heywood Avenue
* Victoria, BC Canada V8V 2Y5
* tel:(250) 361-9093
* mailto:roedy@mindprod.com
*/
/**
* Encode arbitrary binary into printable ASCII using BASE64 encoding.
* very loosely based on the Base64 Reader by
* Dr. Mark Thornton
* Optrak Distribution Software Ltd.
* http://www.optrak.co.uk
* and Kevin Kelley's http://www.ruralnet.net/~kelley/java/Base64.java
* <p>
* Base64 is a way of encoding 8-bit characters using only ASCII printable
* characters similar to UUENCODE. UUENCODE includes a filename where BASE64 does not.
* The spec is described in RFC 2045. Base64 is a scheme where
* 3 bytes are concatenated, then split to form 4 groups of 6-bits each; and
* each 6-bits gets translated to an encoded printable ASCII character, via a
* table lookup. An encoded string is therefore longer than the original by
* about 1/3. The "=" character is used to pad the end. Base64 is used,
* among other things, to encode the user:password string in an
* Authorization: header for HTTP. Don't confuse Base64 with
* x-www-form-urlencoded which is handled by
* Java.net.URLEncoder.encode/decode
* If you don't like this code, there is another implementation at http://www.ruffboy.com/download.htm
* Sun has an undocumented method called sun.misc.Base64Encoder.encode.
* You could use hex, simpler to code, but not as compact.
* <p>
* If you wanted to encode a giant file, you could do it in large chunks that
* are even multiples of 3 bytes, except for the last chunk, and append the outputs.
* <p>
* To encode a string, rather than binary data java.net.URLEncoder may be better. See
* printable characters in the Java glossary for a discussion of the differences.
* <p>
* version 1.4 2002 February 15 -- correct bugs with uneven line lengths,
* allow you to configure line separator.
* now need Base64 object and instance methods.
* new mailing address.
* version 1.3 2000 September 12 -- fix problems with estimating output length in encode
* version 1.2 2000 September 09 -- now handles decode as well.
* version 1.1 1999 December 04 -- more symmetrical encoding algorithm.
* more accurate StringBuffer allocation size.
* version 1.0 1999 December 03 -- posted in comp.lang.java.programmer.
* Futures Streams or files.
*/
public class Base64 {
/**
* Marker value for chars we just ignore, e.g. \n \r high ascii
*/
static final int IGNORE = -1;
/**
* Marker for = trailing pad
*/
static final int PAD = -2;
/**
* used to disable test driver
*/
private static final boolean debug = true;
/**
* how we separate lines, e.g. \n, \r\n, \r etc.
*/
private String lineSeparator = System.getProperty("line.separator");
/**
* max chars per line, excluding lineSeparator. A multiple of 4.
*/
private int lineLength = 72;
private char[] valueToChar = new char[64];
/**
* binary value encoded by a given letter of the alphabet 0..63
*/
private int[] charToValue = new int[256];
private int[] charToPad = new int[4];
/* constructor */
public Base64() {
this.init('+', '/', '=');
}
/* constructor */
public Base64(char chPlus, char chSplash, char chPad, int lineLength) {
this.init(chPlus, chSplash, chPad);
this.lineLength = lineLength;
}
public Base64(int lineLength) {
this.lineLength = lineLength;
}
/**
* debug display array
*/
public static void show(byte[] b) {
int count = 0;
int rows = 0;
for (int i = 0; i < b.length; i++) {
if (count == 8) {
System.out.print(" ");
} else if (count == 16) {
System.out.println("");
count = 0;
continue;
}
System.out.print(Integer.toHexString(b[i] & 0xFF).toUpperCase() + " ");
count++;
}
System.out.println();
}
/**
* debug display array
*/
public static void display(byte[] b) {
for (int i = 0; i < b.length; i++) {
System.out.print((char) b[i]);
}
System.out.println();
}
/**
* test driver
*/
public static void main(String[] args) {
test();
System.exit(1);
if (debug) {
try {
Base64 b64 = new Base64();
String str = "agfrtu¿¦etʲ1234¼Ù´óerty¿Õ234·¢¿¦2344ʲµÄ";
String str64 = "";
//encode
str64 = b64.encode(str.getBytes());
System.out.println(str64);
//decode
byte[] theBytes = b64.decode(str64);
show(theBytes);
String rst = new String(theBytes);
System.out.println(rst);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
//getBytes(String charsetName);
/*
byte[] a = { (byte)0xfc, (byte)0x0f, (byte)0xc0};
byte[] b = { (byte)0x03, (byte)0xf0, (byte)0x3f};
byte[] c = { (byte)0x00, (byte)0x00, (byte)0x00};
byte[] d = { (byte)0xff, (byte)0xff, (byte)0xff};
byte[] e = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1};
byte[] f = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1, (byte)2};
byte[] g = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1, (byte)2, (byte)3};
byte[] h = "AAAAAAAAAAB".getBytes();
show(a);
show(b);
show(c);
show(d);
show(e);
show(f);
show(g);
show(h);
Base64 b64 = new Base64();
show(b64.decode(b64.encode(a)));
show(b64.decode(b64.encode(b)));
show(b64.decode(b64.encode(c)));
show(b64.decode(b64.encode(d)));
show(b64.decode(b64.encode(e)));
show(b64.decode(b64.encode(f)));
show(b64.decode(b64.encode(g)));
show(b64.decode(b64.encode(h)));
b64.setLineLength(8);
show((b64.encode(h)).getBytes());
*/
}
}// end main
public static void test() {
try {
Base64 b64 = new Base64();
//encode
//str64 = b64.encode(str.getBytes());
//System.out.println(str64);
String str64 = "CwUEFYoAAAADjQMC7ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267ELJiY6w05267EI=";
//decode
byte[] theBytes = b64.decode(str64);
show(theBytes);
} catch (Exception e) {
e.printStackTrace();
}
}
/* initialise defaultValueToChar and defaultCharToValue tables */
private void init(char chPlus, char chSplash, char chPad) {
int index = 0;
// build translate this.valueToChar table only once.
// 0..25 -> 'A'..'Z'
for (int i = 'A'; i <= 'Z'; i++) {
this.valueToChar[index++] = (char) i;
}
// 26..51 -> 'a'..'z'
for (int i = 'a'; i <= 'z'; i++) {
this.valueToChar[index++] = (char) i;
}
// 52..61 -> '0'..'9'
for (int i = '0'; i <= '9'; i++) {
this.valueToChar[index++] = (char) i;
}
this.valueToChar[index++] = chPlus;
this.valueToChar[index++] = chSplash;
// build translate defaultCharToValue table only once.
for (int i = 0; i < 256; i++) {
this.charToValue[i] = IGNORE; // default is to ignore
}
for (int i = 0; i < 64; i++) {
this.charToValue[this.valueToChar[i]] = i;
}
this.charToValue[chPad] = PAD;
java.util.Arrays.fill(this.charToPad, chPad);
}
/**
* Encode an arbitrary array of bytes as Base64 printable ASCII.
* It will be broken into lines of 72 chars each. The last line is not
* terminated with a line separator.
* The output will always have an even multiple of data characters,
* exclusive of \n. It is padded out with =.
*/
public String encode(byte[] b) throws IOException {
// Each group or partial group of 3 bytes becomes four chars
// covered quotient
int outputLength = ((b.length + 2) / 3) * 4;
// account for trailing newlines, on all but the very last line
if (lineLength != 0) {
int lines = (outputLength + lineLength - 1) / lineLength - 1;
if (lines > 0) {
outputLength += lines * lineSeparator.length();
}
}
// must be local for recursion to work.
StringBuffer sb = new StringBuffer(outputLength);
// must be local for recursion to work.
int linePos = 0;
// first deal with even multiples of 3 bytes.
int len = (b.length / 3) * 3;
int leftover = b.length - len;
for (int i = 0; i < len; i += 3) {
// Start a new line if next 4 chars won't fit on the current line
// We can't encapsulete the following code since the variable need to
// be local to this incarnation of encode.
linePos += 4;
if (linePos > lineLength) {
if (lineLength != 0) {
sb.append(lineSeparator);
}
linePos = 4;
}
// get next three bytes in unsigned form lined up,
// in big-endian order
int combined = b[i + 0] & 0xff;
combined <<= 8;
combined |= b[i + 1] & 0xff;
combined <<= 8;
combined |= b[i + 2] & 0xff;
// break those 24 bits into a 4 groups of 6 bits,
// working LSB to MSB.
int c3 = combined & 0x3f;
combined >>>= 6;
int c2 = combined & 0x3f;
combined >>>= 6;
int c1 = combined & 0x3f;
combined >>>= 6;
int c0 = combined & 0x3f;
// Translate into the equivalent alpha character
// emitting them in big-endian order.
sb.append(valueToChar[c0]);
sb.append(valueToChar[c1]);
sb.append(valueToChar[c2]);
sb.append(valueToChar[c3]);
}
// deal with leftover bytes
switch (leftover) {
case 0:
default:
// nothing to do
break;
case 1:
// One leftover byte generates xx==
// Start a new line if next 4 chars won't fit on the current line
linePos += 4;
if (linePos > lineLength) {
if (lineLength != 0) {
sb.append(lineSeparator);
}
linePos = 4;
}
// Handle this recursively with a faked complete triple.
// Throw away last two chars and replace with ==
sb.append(encode(new byte[]{b[len], 0, 0}
).substring(0, 2));
sb.append("==");
break;
case 2:
// Two leftover bytes generates xxx=
// Start a new line if next 4 chars won't fit on the current line
linePos += 4;
if (linePos > lineLength) {
if (lineLength != 0) {
sb.append(lineSeparator);
}
linePos = 4;
}
// Handle this recursively with a faked complete triple.
// Throw away last char and replace with =
sb.append(encode(new byte[]{b[len], b[len + 1], 0}
).substring(0, 3));
sb.append("=");
break;
} // end switch;
if (outputLength != sb.length()) {
System.out.println("oops: minor program flaw: output length mis-estimated");
System.out.println("estimate:" + outputLength);
System.out.println("actual:" + sb.length());
}
return sb.toString();
}// end encode
/**
* decode a well-formed complete Base64 string back into an array of bytes.
* It must have an even multiple of 4 data characters (not counting \n),
* padded out with = as needed.
*/
public byte[] decodeAuto(String s) {
int nRemain = s.length() % 4;
if (nRemain == 0) {
return this.decode(s);
} else {
return this.decode(s + new String(this.charToPad, 0, 4 - nRemain));
}
}
/**
* decode a well-formed complete Base64 string back into an array of bytes.
* It must have an even multiple of 4 data characters (not counting \n),
* padded out with = as needed.
*/
public byte[] decode(String s) {
// estimate worst case size of output array, no embedded newlines.
byte[] b = new byte[(s.length() / 4) * 3];
// tracks where we are in a cycle of 4 input chars.
int cycle = 0;
// where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
int combined = 0;
// how many bytes we have prepared.
int j = 0;
// will be an even multiple of 4 chars, plus some embedded \n
int len = s.length();
int dummies = 0;
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
int value = (c <= 255) ? charToValue[c] : IGNORE;
// there are two magic values PAD (=) and IGNORE.
switch (value) {
case IGNORE:
// e.g. \n, just ignore it.
break;
case PAD:
value = 0;
dummies++;
// fallthrough
default:
/* regular value character */
switch (cycle) {
case 0:
combined = value;
cycle = 1;
break;
case 1:
combined <<= 6;
combined |= value;
cycle = 2;
break;
case 2:
combined <<= 6;
combined |= value;
cycle = 3;
break;
case 3:
combined <<= 6;
combined |= value;
// we have just completed a cycle of 4 chars.
// the four 6-bit values are in combined in big-endian order
// peel them off 8 bits at a time working lsb to msb
// to get our original 3 8-bit bytes back
b[j + 2] = (byte) combined;
combined >>>= 8;
b[j + 1] = (byte) combined;
combined >>>= 8;
b[j] = (byte) combined;
j += 3;
cycle = 0;
break;
}
break;
}
} // end for
if (cycle != 0) {
throw new ArrayIndexOutOfBoundsException("Input to decode not an even multiple of 4 characters; pad with =.");
}
j -= dummies;
if (b.length != j) {
byte[] b2 = new byte[j];
System.arraycopy(b, 0, b2, 0, j);
b = b2;
}
return b;
}// end decode
/**
* determines how long the lines are that are generated by encode.
* Ignored by decode.
*
* @param length 0 means no newlines inserted. Must be a multiple of 4.
*/
public void setLineLength(int length) {
this.lineLength = (length / 4) * 4;
}
/**
* How lines are separated.
* Ignored by decode.
*
* @param lineSeparator may be "" but not null.
* Usually contains only a combination of chars \n and \r.
* Could be any chars not in set A-Z a-z 0-9 + /.
*/
public void setLineSeparator(String lineSeparator) {
this.lineSeparator = lineSeparator;
}
} // end Base64