1package org.bouncycastle.util.encoders;
2
3import java.io.IOException;
4import java.io.OutputStream;
5
6public class Base64Encoder
7    implements Encoder
8{
9    protected final byte[] encodingTable =
10        {
11            (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
12            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
13            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
14            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
15            (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
16            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
17            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
18            (byte)'v',
19            (byte)'w', (byte)'x', (byte)'y', (byte)'z',
20            (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
21            (byte)'7', (byte)'8', (byte)'9',
22            (byte)'+', (byte)'/'
23        };
24
25    protected byte    padding = (byte)'=';
26
27    /*
28     * set up the decoding table.
29     */
30    protected final byte[] decodingTable = new byte[128];
31
32    protected void initialiseDecodingTable()
33    {
34        for (int i = 0; i < decodingTable.length; i++)
35        {
36            decodingTable[i] = (byte)0xff;
37        }
38
39        for (int i = 0; i < encodingTable.length; i++)
40        {
41            decodingTable[encodingTable[i]] = (byte)i;
42        }
43    }
44
45    public Base64Encoder()
46    {
47        initialiseDecodingTable();
48    }
49
50    /**
51     * encode the input data producing a base 64 output stream.
52     *
53     * @return the number of bytes produced.
54     */
55    public int encode(
56        byte[]                data,
57        int                    off,
58        int                    length,
59        OutputStream    out)
60        throws IOException
61    {
62        int modulus = length % 3;
63        int dataLength = (length - modulus);
64        int a1, a2, a3;
65
66        for (int i = off; i < off + dataLength; i += 3)
67        {
68            a1 = data[i] & 0xff;
69            a2 = data[i + 1] & 0xff;
70            a3 = data[i + 2] & 0xff;
71
72            out.write(encodingTable[(a1 >>> 2) & 0x3f]);
73            out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
74            out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
75            out.write(encodingTable[a3 & 0x3f]);
76        }
77
78        /*
79         * process the tail end.
80         */
81        int    b1, b2, b3;
82        int    d1, d2;
83
84        switch (modulus)
85        {
86        case 0:        /* nothing left to do */
87            break;
88        case 1:
89            d1 = data[off + dataLength] & 0xff;
90            b1 = (d1 >>> 2) & 0x3f;
91            b2 = (d1 << 4) & 0x3f;
92
93            out.write(encodingTable[b1]);
94            out.write(encodingTable[b2]);
95            out.write(padding);
96            out.write(padding);
97            break;
98        case 2:
99            d1 = data[off + dataLength] & 0xff;
100            d2 = data[off + dataLength + 1] & 0xff;
101
102            b1 = (d1 >>> 2) & 0x3f;
103            b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
104            b3 = (d2 << 2) & 0x3f;
105
106            out.write(encodingTable[b1]);
107            out.write(encodingTable[b2]);
108            out.write(encodingTable[b3]);
109            out.write(padding);
110            break;
111        }
112
113        return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
114    }
115
116    private boolean ignore(
117        char    c)
118    {
119        return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
120    }
121
122    /**
123     * decode the base 64 encoded byte data writing it to the given output stream,
124     * whitespace characters will be ignored.
125     *
126     * @return the number of bytes produced.
127     */
128    public int decode(
129        byte[]          data,
130        int             off,
131        int             length,
132        OutputStream    out)
133        throws IOException
134    {
135        byte    b1, b2, b3, b4;
136        int     outLen = 0;
137
138        int     end = off + length;
139
140        while (end > off)
141        {
142            if (!ignore((char)data[end - 1]))
143            {
144                break;
145            }
146
147            end--;
148        }
149
150        int  i = off;
151        int  finish = end - 4;
152
153        i = nextI(data, i, finish);
154
155        while (i < finish)
156        {
157            b1 = decodingTable[data[i++]];
158
159            i = nextI(data, i, finish);
160
161            b2 = decodingTable[data[i++]];
162
163            i = nextI(data, i, finish);
164
165            b3 = decodingTable[data[i++]];
166
167            i = nextI(data, i, finish);
168
169            b4 = decodingTable[data[i++]];
170
171            if ((b1 | b2 | b3 | b4) < 0)
172            {
173                throw new IOException("invalid characters encountered in base64 data");
174            }
175
176            out.write((b1 << 2) | (b2 >> 4));
177            out.write((b2 << 4) | (b3 >> 2));
178            out.write((b3 << 6) | b4);
179
180            outLen += 3;
181
182            i = nextI(data, i, finish);
183        }
184
185        outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]);
186
187        return outLen;
188    }
189
190    private int nextI(byte[] data, int i, int finish)
191    {
192        while ((i < finish) && ignore((char)data[i]))
193        {
194            i++;
195        }
196        return i;
197    }
198
199    /**
200     * decode the base 64 encoded String data writing it to the given output stream,
201     * whitespace characters will be ignored.
202     *
203     * @return the number of bytes produced.
204     */
205    public int decode(
206        String          data,
207        OutputStream    out)
208        throws IOException
209    {
210        byte    b1, b2, b3, b4;
211        int     length = 0;
212
213        int     end = data.length();
214
215        while (end > 0)
216        {
217            if (!ignore(data.charAt(end - 1)))
218            {
219                break;
220            }
221
222            end--;
223        }
224
225        int  i = 0;
226        int  finish = end - 4;
227
228        i = nextI(data, i, finish);
229
230        while (i < finish)
231        {
232            b1 = decodingTable[data.charAt(i++)];
233
234            i = nextI(data, i, finish);
235
236            b2 = decodingTable[data.charAt(i++)];
237
238            i = nextI(data, i, finish);
239
240            b3 = decodingTable[data.charAt(i++)];
241
242            i = nextI(data, i, finish);
243
244            b4 = decodingTable[data.charAt(i++)];
245
246            if ((b1 | b2 | b3 | b4) < 0)
247            {
248                throw new IOException("invalid characters encountered in base64 data");
249            }
250
251            out.write((b1 << 2) | (b2 >> 4));
252            out.write((b2 << 4) | (b3 >> 2));
253            out.write((b3 << 6) | b4);
254
255            length += 3;
256
257            i = nextI(data, i, finish);
258        }
259
260        length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1));
261
262        return length;
263    }
264
265    private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4)
266        throws IOException
267    {
268        byte    b1, b2, b3, b4;
269
270        if (c3 == padding)
271        {
272            b1 = decodingTable[c1];
273            b2 = decodingTable[c2];
274
275            if ((b1 | b2) < 0)
276            {
277                throw new IOException("invalid characters encountered at end of base64 data");
278            }
279
280            out.write((b1 << 2) | (b2 >> 4));
281
282            return 1;
283        }
284        else if (c4 == padding)
285        {
286            b1 = decodingTable[c1];
287            b2 = decodingTable[c2];
288            b3 = decodingTable[c3];
289
290            if ((b1 | b2 | b3) < 0)
291            {
292                throw new IOException("invalid characters encountered at end of base64 data");
293            }
294
295            out.write((b1 << 2) | (b2 >> 4));
296            out.write((b2 << 4) | (b3 >> 2));
297
298            return 2;
299        }
300        else
301        {
302            b1 = decodingTable[c1];
303            b2 = decodingTable[c2];
304            b3 = decodingTable[c3];
305            b4 = decodingTable[c4];
306
307            if ((b1 | b2 | b3 | b4) < 0)
308            {
309                throw new IOException("invalid characters encountered at end of base64 data");
310            }
311
312            out.write((b1 << 2) | (b2 >> 4));
313            out.write((b2 << 4) | (b3 >> 2));
314            out.write((b3 << 6) | b4);
315
316            return 3;
317        }
318    }
319
320    private int nextI(String data, int i, int finish)
321    {
322        while ((i < finish) && ignore(data.charAt(i)))
323        {
324            i++;
325        }
326        return i;
327    }
328}
329