1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18/**
19* @author Alexander Y. Kleymenov
20*/
21
22package libcore.io;
23
24import java.nio.charset.Charsets;
25import libcore.util.EmptyArray;
26
27/**
28 * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder.
29 * In violation of the RFC, this encoder doesn't wrap lines at 76 columns.
30 */
31public final class Base64 {
32    private Base64() {
33    }
34
35    public static byte[] decode(byte[] in) {
36        return decode(in, in.length);
37    }
38
39    public static byte[] decode(byte[] in, int len) {
40        // approximate output length
41        int length = len / 4 * 3;
42        // return an empty array on empty or short input without padding
43        if (length == 0) {
44            return EmptyArray.BYTE;
45        }
46        // temporary array
47        byte[] out = new byte[length];
48        // number of padding characters ('=')
49        int pad = 0;
50        byte chr;
51        // compute the number of the padding characters
52        // and adjust the length of the input
53        for (;;len--) {
54            chr = in[len-1];
55            // skip the neutral characters
56            if ((chr == '\n') || (chr == '\r') ||
57                    (chr == ' ') || (chr == '\t')) {
58                continue;
59            }
60            if (chr == '=') {
61                pad++;
62            } else {
63                break;
64            }
65        }
66        // index in the output array
67        int outIndex = 0;
68        // index in the input array
69        int inIndex = 0;
70        // holds the value of the input character
71        int bits = 0;
72        // holds the value of the input quantum
73        int quantum = 0;
74        for (int i=0; i<len; i++) {
75            chr = in[i];
76            // skip the neutral characters
77            if ((chr == '\n') || (chr == '\r') ||
78                    (chr == ' ') || (chr == '\t')) {
79                continue;
80            }
81            if ((chr >= 'A') && (chr <= 'Z')) {
82                // char ASCII value
83                //  A    65    0
84                //  Z    90    25 (ASCII - 65)
85                bits = chr - 65;
86            } else if ((chr >= 'a') && (chr <= 'z')) {
87                // char ASCII value
88                //  a    97    26
89                //  z    122   51 (ASCII - 71)
90                bits = chr - 71;
91            } else if ((chr >= '0') && (chr <= '9')) {
92                // char ASCII value
93                //  0    48    52
94                //  9    57    61 (ASCII + 4)
95                bits = chr + 4;
96            } else if (chr == '+') {
97                bits = 62;
98            } else if (chr == '/') {
99                bits = 63;
100            } else {
101                return null;
102            }
103            // append the value to the quantum
104            quantum = (quantum << 6) | (byte) bits;
105            if (inIndex%4 == 3) {
106                // 4 characters were read, so make the output:
107                out[outIndex++] = (byte) (quantum >> 16);
108                out[outIndex++] = (byte) (quantum >> 8);
109                out[outIndex++] = (byte) quantum;
110            }
111            inIndex++;
112        }
113        if (pad > 0) {
114            // adjust the quantum value according to the padding
115            quantum = quantum << (6*pad);
116            // make output
117            out[outIndex++] = (byte) (quantum >> 16);
118            if (pad == 1) {
119                out[outIndex++] = (byte) (quantum >> 8);
120            }
121        }
122        // create the resulting array
123        byte[] result = new byte[outIndex];
124        System.arraycopy(out, 0, result, 0, outIndex);
125        return result;
126    }
127
128    private static final byte[] map = new byte[]
129        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
130         'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
131         'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
132         'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
133         '4', '5', '6', '7', '8', '9', '+', '/'};
134
135    public static String encode(byte[] in) {
136        int length = (in.length + 2) * 4 / 3;
137        byte[] out = new byte[length];
138        int index = 0, end = in.length - in.length % 3;
139        for (int i = 0; i < end; i += 3) {
140            out[index++] = map[(in[i] & 0xff) >> 2];
141            out[index++] = map[((in[i] & 0x03) << 4) | ((in[i+1] & 0xff) >> 4)];
142            out[index++] = map[((in[i+1] & 0x0f) << 2) | ((in[i+2] & 0xff) >> 6)];
143            out[index++] = map[(in[i+2] & 0x3f)];
144        }
145        switch (in.length % 3) {
146            case 1:
147                out[index++] = map[(in[end] & 0xff) >> 2];
148                out[index++] = map[(in[end] & 0x03) << 4];
149                out[index++] = '=';
150                out[index++] = '=';
151                break;
152            case 2:
153                out[index++] = map[(in[end] & 0xff) >> 2];
154                out[index++] = map[((in[end] & 0x03) << 4) | ((in[end+1] & 0xff) >> 4)];
155                out[index++] = map[((in[end+1] & 0x0f) << 2)];
156                out[index++] = '=';
157                break;
158        }
159        return new String(out, 0, index, Charsets.US_ASCII);
160    }
161}
162