001package lucee.commons.digest; 002 003import java.util.Arrays; 004import java.util.HashMap; 005import java.util.Map; 006 007import lucee.commons.io.CharsetUtil; 008import lucee.runtime.coder.CoderException; 009 010public class Base64Encoder { 011 012 private static final char[] ALPHABET = new char[] { 013 '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', 014 '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', 015 '0','1','2','3','4','5','6','7','8','9','+','/' 016 }; 017 private static final char PAD = '='; 018 019 020 private static final Map<Character,Integer> REVERSE = new HashMap<Character,Integer>(); 021 static { 022 for (int i=0; i<64; i++) { 023 REVERSE.put(ALPHABET[i], i); 024 } 025 REVERSE.put('-', 62); 026 REVERSE.put('_', 63); 027 REVERSE.put(PAD, 0); 028 } 029 030 031 public static String encodeFromString(String data) { 032 return encode(data.getBytes(CharsetUtil.UTF8)); 033 } 034 035 036 037 /** 038 * Translates the specified byte array into Base64 string. 039 * 040 * @param data the byte array (not null) 041 * @return the translated Base64 string (not null) 042 */ 043 public static String encode(byte[] data) { 044 StringBuilder builder = new StringBuilder(); 045 for (int position=0; position < data.length; position+=3) { 046 builder.append(encodeGroup(data, position)); 047 } 048 return builder.toString(); 049 } 050 051 052 053 054 055 056 057 //// Helper methods 058 059 060 /** 061 * Encode three bytes of data into four characters. 062 */ 063 private static char[] encodeGroup(byte[] data, int position) { 064 final char[] c = new char[] { '=','=','=','=' }; 065 int b1=0, b2=0, b3=0; 066 int length = data.length - position; 067 068 if (length == 0) 069 return c; 070 071 if (length >= 1) { 072 b1 = (data[position])&0xFF; 073 } 074 if (length >= 2) { 075 b2 = (data[position+1])&0xFF; 076 } 077 if (length >= 3) { 078 b3 = (data[position+2])&0xFF; 079 } 080 081 c[0] = ALPHABET[b1>>2]; 082 c[1] = ALPHABET[(b1 & 3)<<4 | (b2>>4)]; 083 if (length == 1) 084 return c; 085 c[2] = ALPHABET[(b2 & 15)<<2 | (b3>>6)]; 086 if (length == 2) 087 return c; 088 c[3] = ALPHABET[b3 & 0x3f]; 089 return c; 090 } 091 092 public static String decodeAsString(String data) throws CoderException { 093 return new String(decode(data),CharsetUtil.UTF8); 094 } 095 096 097 098 099 /** 100 * Translates the specified Base64 string into a byte array. 101 * 102 * @param s the Base64 string (not null) 103 * @return the byte array (not null) 104 * @throws CoderException 105 */ 106 public static byte[] decode(String data) throws CoderException { 107 byte[] array = new byte[data.length()*3/4]; 108 char[] block = new char[4]; 109 int length = 0; 110 data=data.trim(); 111 final int len=data.length(); 112 if(len==0) return new byte[0];// we accept a empty string as a empty binary! 113 if(len % 4 != 0 || len < 4) 114 throw new CoderException("can't decode the base64 input string"+printString(data)+", because the input string has an invalid length"); 115 116 117 for (int position=0; position < len; ) { 118 int p; 119 for (p=0; p<4 && position < data.length(); position++) { 120 char c = data.charAt(position); 121 if (!Character.isWhitespace(c)) { 122 block[p] = c; 123 p++; 124 } 125 } 126 127 if (p==0) 128 break; 129 130 131 int l = decodeGroup(block, array, length); 132 length += l; 133 if (l < 3) 134 break; 135 } 136 return Arrays.copyOf(array, length); 137 } 138 139 /** 140 * Decode four chars from data into 0-3 bytes of data starting at position in array. 141 * @return the number of bytes decoded. 142 */ 143 private static int decodeGroup(char[] data, byte[] array, int position) throws CoderException { 144 int b1, b2, b3, b4; 145 146 try { 147 b1 = REVERSE.get(data[0]); 148 b2 = REVERSE.get(data[1]); 149 b3 = REVERSE.get(data[2]); 150 b4 = REVERSE.get(data[3]); 151 152 } catch (NullPointerException e) { 153 // If auto-boxing fails 154 throw new CoderException("Illegal characters in the sequence to be "+ 155 "decoded: "+Arrays.toString(data)); 156 } 157 158 array[position] = (byte)((b1 << 2) | (b2 >> 4)); 159 array[position+1] = (byte)((b2 << 4) | (b3 >> 2)); 160 array[position+2] = (byte)((b3 << 6) | (b4)); 161 162 // Check the amount of data decoded 163 if (data[0] == PAD) 164 return 0; 165 if (data[1] == PAD) { 166 throw new CoderException("Illegal character padding in sequence to be "+ 167 "decoded: "+Arrays.toString(data)); 168 } 169 if (data[2] == PAD) 170 return 1; 171 if (data[3] == PAD) 172 return 2; 173 174 return 3; 175 } 176 private static String printString(String s) { 177 if(s.length()>50) return " ["+s.substring(0,50)+" ... truncated]"; 178 return " ["+s+"]"; 179 } 180}