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}