/**
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
**/
package lucee.commons.digest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import lucee.commons.io.CharsetUtil;
import lucee.runtime.coder.CoderException;
public class Base64Encoder {
private static final char[] ALPHABET = new char[] {
'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 char PAD = '=';
private static final Map<Character,Integer> REVERSE = new HashMap<Character,Integer>();
static {
for (int i=0; i<64; i++) {
REVERSE.put(ALPHABET[i], i);
}
REVERSE.put('-', 62);
REVERSE.put('_', 63);
REVERSE.put(PAD, 0);
}
public static String encodeFromString(String data) {
return encode(data.getBytes(CharsetUtil.UTF8));
}
/**
* Translates the specified byte array into Base64 string.
*
* @param data the byte array (not null)
* @return the translated Base64 string (not null)
*/
public static String encode(byte[] data) {
StringBuilder builder = new StringBuilder();
for (int position=0; position < data.length; position+=3) {
builder.append(encodeGroup(data, position));
}
return builder.toString();
}
//// Helper methods
/**
* Encode three bytes of data into four characters.
*/
private static char[] encodeGroup(byte[] data, int position) {
final char[] c = new char[] { '=','=','=','=' };
int b1=0, b2=0, b3=0;
int length = data.length - position;
if (length == 0)
return c;
if (length >= 1) {
b1 = (data[position])&0xFF;
}
if (length >= 2) {
b2 = (data[position+1])&0xFF;
}
if (length >= 3) {
b3 = (data[position+2])&0xFF;
}
c[0] = ALPHABET[b1>>2];
c[1] = ALPHABET[(b1 & 3)<<4 | (b2>>4)];
if (length == 1)
return c;
c[2] = ALPHABET[(b2 & 15)<<2 | (b3>>6)];
if (length == 2)
return c;
c[3] = ALPHABET[b3 & 0x3f];
return c;
}
public static String decodeAsString(String data) throws CoderException {
return new String(decode(data),CharsetUtil.UTF8);
}
/**
* Translates the specified Base64 string into a byte array.
*
* @param s the Base64 string (not null)
* @return the byte array (not null)
* @throws CoderException
*/
public static byte[] decode(String data) throws CoderException {
byte[] array = new byte[data.length()*3/4];
char[] block = new char[4];
int length = 0;
data=data.trim();
final int len=data.length();
if(len==0) return new byte[0];// we accept a empty string as a empty binary!
if(len % 4 != 0 || len < 4)
throw new CoderException("can't decode the the base64 input string"+printString(data)+", because the input string has an invalid length");
for (int position=0; position < len; ) {
int p;
for (p=0; p<4 && position < data.length(); position++) {
char c = data.charAt(position);
if (!Character.isWhitespace(c)) {
block[p] = c;
p++;
}
}
if (p==0)
break;
int l = decodeGroup(block, array, length);
length += l;
if (l < 3)
break;
}
return Arrays.copyOf(array, length);
}
/**
* Decode four chars from data into 0-3 bytes of data starting at position in array.
* @return the number of bytes decoded.
*/
private static int decodeGroup(char[] data, byte[] array, int position) throws CoderException {
int b1, b2, b3, b4;
try {
b1 = REVERSE.get(data[0]);
b2 = REVERSE.get(data[1]);
b3 = REVERSE.get(data[2]);
b4 = REVERSE.get(data[3]);
} catch (NullPointerException e) {
// If auto-boxing fails
throw new CoderException("Illegal characters in the sequence to be "+
"decoded: "+Arrays.toString(data));
}
array[position] = (byte)((b1 << 2) | (b2 >> 4));
array[position+1] = (byte)((b2 << 4) | (b3 >> 2));
array[position+2] = (byte)((b3 << 6) | (b4));
// Check the amount of data decoded
if (data[0] == PAD)
return 0;
if (data[1] == PAD) {
throw new CoderException("Illegal character padding in sequence to be "+
"decoded: "+Arrays.toString(data));
}
if (data[2] == PAD)
return 1;
if (data[3] == PAD)
return 2;
return 3;
}
private static String printString(String s) {
if(s.length()>50) return " ["+s.substring(0,50)+" ... truncated]";
return " ["+s+"]";
}
}