package net.sf.openrocket.util; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class Base64 { public static final int DEFAULT_CHARS_PER_LINE = 72; 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 byte[] REVERSE; // static { // REVERSE = new byte[128]; // Arrays.fill(REVERSE, (byte)-1); // for (int i=0; i<64; i++) { // REVERSE[ALPHABET[i]] = (byte)i; // } // REVERSE['-'] = 62; // REVERSE['_'] = 63; // REVERSE[PAD] = 0; // } 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 encode(byte[] data) { return encode(data, DEFAULT_CHARS_PER_LINE); } public static String encode(byte[] data, int maxColumn) { StringBuilder builder = new StringBuilder(); int column = 0; for (int position=0; position < data.length; position+=3) { if (column+4 > maxColumn) { builder.append('\n'); column = 0; } builder.append(encodeGroup(data, position)); column += 4; } builder.append('\n'); return builder.toString(); } public static byte[] decode(String data) { byte[] array = new byte[data.length()*3/4]; char[] block = new char[4]; int length = 0; for (int position=0; position < data.length(); ) { 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; if (p!=4) { throw new IllegalArgumentException("Data ended when decoding Base64, data=" + data + ", p="+p); } int l = decodeGroup(block, array, length); length += l; if (l < 3) break; } return Arrays.copyOf(array, length); } //// Helper methods /** * Encode three bytes of data into four characters. */ private static char[] encodeGroup(byte[] data, int position) { 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 = ((int)data[position])&0xFF; } if (length >= 2) { b2 = ((int)data[position+1])&0xFF; } if (length >= 3) { b3 = ((int)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; } /** * 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) { 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 IllegalArgumentException("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 IllegalArgumentException("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; } }