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
18package libcore.io;
19
20import java.nio.ByteBuffer;
21import java.nio.CharBuffer;
22import java.nio.charset.CharacterCodingException;
23import java.nio.charset.CharsetDecoder;
24import java.nio.charset.CharsetEncoder;
25import java.nio.charset.CodingErrorAction;
26import java.nio.charset.StandardCharsets;
27import java.util.Arrays;
28
29import junit.framework.AssertionFailedError;
30import junit.framework.TestCase;
31
32public final class Base64Test extends TestCase {
33
34    public void testEncodeDecode() throws Exception {
35        assertEncodeDecode("");
36        assertEncodeDecode("Eg==", 0x12);
37        assertEncodeDecode("EjQ=", 0x12, 0x34);
38        assertEncodeDecode("EjRW", 0x12, 0x34, 0x56);
39        assertEncodeDecode("EjRWeA==", 0x12, 0x34, 0x56, 0x78);
40        assertEncodeDecode("EjRWeJo=", 0x12, 0x34, 0x56, 0x78, 0x9A);
41        assertEncodeDecode("EjRWeJq8", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc);
42    }
43
44    public void testEncode_doesNotWrap() throws Exception {
45        int[] data = new int[61];
46        Arrays.fill(data, 0xff);
47        String expected = "///////////////////////////////////////////////////////////////////////"
48                + "//////////w=="; // 84 chars
49        assertEncodeDecode(expected, data);
50    }
51
52    private static void assertEncodeDecode(String expectedEncoded, int... toEncode)
53            throws Exception {
54        // We should never expect (or receive) non-ASCII text from Base64.encoder.
55        asciiToBytes(expectedEncoded);
56
57        // Convert the convenient ints to the bytes we need.
58        byte[] inputBytes = new byte[toEncode.length];
59        for (int i = 0; i < toEncode.length; i++) {
60            inputBytes[i] = (byte) toEncode[i];
61        }
62        String encoded = Base64.encode(inputBytes);
63        assertEquals(expectedEncoded, encoded);
64
65        // Check we can round-trip the encoded bytes to
66        // arrive at what we started with.
67        int[] actualDecodedBytes = decodeToInts(encoded);
68        assertArrayEquals(toEncode, actualDecodedBytes);
69    }
70
71    public void testDecode_empty() throws Exception {
72        byte[] decoded = Base64.decode(new byte[0]);
73        assertEquals(0, decoded.length);
74    }
75
76    public void testDecode_truncated() throws Exception {
77        // Correct data, for reference.
78        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk"));
79
80        // The following are missing the final bytes
81        assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcmx"));
82        assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcm"));
83        assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvc"));
84        assertEquals("hello, wo", decodeToString("aGVsbG8sIHdv"));
85    }
86
87    public void testDecode_extraChars() throws Exception {
88        // Characters outside of alphabet before padding.
89        assertEquals("hello, world", decodeToString(" aGVsbG8sIHdvcmxk"));
90        assertEquals("hello, world", decodeToString("aGV sbG8sIHdvcmxk"));
91        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk "));
92        assertEquals(null, decodeToString("*aGVsbG8sIHdvcmxk"));
93        assertEquals(null, decodeToString("aGV*sbG8sIHdvcmxk"));
94        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk*"));
95        assertEquals("hello, world", decodeToString("\r\naGVsbG8sIHdvcmxk"));
96        assertEquals("hello, world", decodeToString("aGV\r\nsbG8sIHdvcmxk"));
97        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\r\n"));
98        assertEquals("hello, world", decodeToString("\naGVsbG8sIHdvcmxk"));
99        assertEquals("hello, world", decodeToString("aGV\nsbG8sIHdvcmxk"));
100        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\n"));
101
102        // padding 0
103        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk"));
104        // Extra padding
105        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk="));
106        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk=="));
107        // Characters outside alphabet intermixed with (too much) padding.
108        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk ="));
109        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk = = "));
110
111        // padding 1
112        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE="));
113        // Missing padding
114        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkPyE"));
115        // Characters outside alphabet before padding.
116        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE ="));
117        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE*="));
118        // Trailing characters, otherwise valid.
119        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= "));
120        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=*"));
121        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=X"));
122        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XY"));
123        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZ"));
124        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZA"));
125        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\n"));
126        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\r\n"));
127        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= "));
128        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=="));
129        // Whitespace characters outside alphabet intermixed with (too much) padding.
130        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE =="));
131        assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE = = "));
132
133        // padding 2
134        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg=="));
135        // Missing padding
136        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg"));
137        // Partially missing padding
138        assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg="));
139        // Characters outside alphabet before padding.
140        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg =="));
141        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg*=="));
142        // Trailing characters, otherwise valid.
143        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== "));
144        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==*"));
145        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==X"));
146        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XY"));
147        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZ"));
148        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZA"));
149        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\n"));
150        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\r\n"));
151        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== "));
152        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==="));
153        // Characters outside alphabet inside padding.
154        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg= ="));
155        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=*="));
156        assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg=\r\n="));
157        // Characters inside alphabet inside padding.
158        assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=X="));
159
160        // Table 1 chars
161        assertEquals(null, decodeToString("_aGVsbG8sIHdvcmx"));
162        assertEquals(null, decodeToString("aGV_sbG8sIHdvcmx"));
163        assertEquals(null, decodeToString("aGVsbG8sIHdvcmx_"));
164
165        // Table 2 chars.
166        assertArrayEquals(
167                new int[] {0xfd, 0xa1, 0x95, 0xb1, 0xb1, 0xbc, 0xb0, 0x81, 0xdd, 0xbd, 0xc9,
168                        0xb1 },
169                decodeToInts("/aGVsbG8sIHdvcmx"));
170        assertArrayEquals(
171                new int[] { 0x68, 0x65, 0x7f, 0xb1, 0xb1, 0xbc, 0xb0, 0x81, 0xdd, 0xbd, 0xc9,
172                        0xb1 },
173                decodeToInts("aGV/sbG8sIHdvcmx"));
174        assertArrayEquals(
175                new int[] { 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 0x7f },
176                decodeToInts("aGVsbG8sIHdvcmx/"));
177    }
178
179    private static final int[] BYTE_VALUES = {
180            0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77
181    };
182
183    public void testDecode_nonAsciiBytes() throws Exception {
184        assertSubArrayEquals(BYTE_VALUES, 0, decodeToInts(""));
185        assertSubArrayEquals(BYTE_VALUES, 1, decodeToInts("/w=="));
186        assertSubArrayEquals(BYTE_VALUES, 2, decodeToInts("/+4="));
187        assertSubArrayEquals(BYTE_VALUES, 3, decodeToInts("/+7d"));
188        assertSubArrayEquals(BYTE_VALUES, 4, decodeToInts("/+7dzA=="));
189        assertSubArrayEquals(BYTE_VALUES, 5, decodeToInts("/+7dzLs="));
190        assertSubArrayEquals(BYTE_VALUES, 6, decodeToInts("/+7dzLuq"));
191        assertSubArrayEquals(BYTE_VALUES, 7, decodeToInts("/+7dzLuqmQ=="));
192        assertSubArrayEquals(BYTE_VALUES, 8, decodeToInts("/+7dzLuqmYg="));
193    }
194
195    public void testDecode_urlAlphabet() throws Exception {
196        assertNull(decodeToInts("_w=="));
197        assertNull(decodeToInts("-w=="));
198    }
199
200    /**
201     * Convenience function for decoding from a Base64 ASCII String to an ASCII String. A String is
202     * used for the output to make the tests compact. Can return null if the decoder returns null.
203     * If any of the strings involved are non-ASCII an exception is thrown.
204     * Use {@link #decodeToInts(String)} for decode tests that produce bytes
205     * outside of the ASCII range.
206     */
207    private static String decodeToString(String in) throws Exception {
208        byte[] bytes = asciiToBytes(in);
209        byte[] out = Base64.decode(bytes);
210        if (out == null) {
211            return null;
212        }
213        return bytesToAscii(out);
214    }
215
216    private static String bytesToAscii(byte[] bytes) {
217        try {
218            CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
219            decoder.onMalformedInput(CodingErrorAction.REPORT);
220            decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
221            ByteBuffer bytesBuffer = ByteBuffer.wrap(bytes);
222            CharBuffer charsBuffer = decoder.decode(bytesBuffer);
223            char[] chars = new char[charsBuffer.remaining()];
224            charsBuffer.get(chars, 0, chars.length);
225            return new String(chars);
226        } catch (CharacterCodingException e) {
227            // Use bytes in your test, not Strings.
228            throw new AssertionFailedError("Cannot convert test bytes to String safely: " +
229                    Arrays.toString(bytesToInts(bytes)) + " contains non-ASCII codes");
230        }
231    }
232
233    private static byte[] asciiToBytes(String string) {
234        try {
235            char[] chars = string.toCharArray();
236
237            CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder();
238            encoder.onMalformedInput(CodingErrorAction.REPORT);
239            encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
240            CharBuffer charsBuffer = CharBuffer.wrap(chars);
241            ByteBuffer bytesBuffer = encoder.encode(charsBuffer);
242            byte[] bytes = new byte[bytesBuffer.remaining()];
243            bytesBuffer.get(bytes, 0, bytes.length);
244            return bytes;
245        } catch (CharacterCodingException e) {
246            // Use bytes in your test, not Strings.
247            throw new AssertionFailedError("Cannot convert test String to bytes safely: " + string +
248                    " contains non-ASCII characters");
249        }
250    }
251
252    /** Decodes an ASCII string, returning an int array. */
253    private static int[] decodeToInts(String in) throws Exception {
254        byte[] bytes = Base64.decode(asciiToBytes(in));
255        return bytesToInts(bytes);
256    }
257
258    /**
259     * Convert a byte[] to an int[]. int is used because it is more convenient to use ints in
260     * tests.
261     */
262    private static int[] bytesToInts(byte[] bytes) {
263        if (bytes == null) {
264            return null;
265        }
266        int[] ints = new int[bytes.length];
267        for (int i = 0; i < bytes.length; i++) {
268            ints[i] = bytes[i] & 0xff;
269        }
270        return ints;
271    }
272
273    /** Assert that decoding 'in' throws ArrayIndexOutOfBoundsException. */
274    private static void assertDecodeBad(String in) throws Exception {
275        try {
276            byte[] result = Base64.decode(asciiToBytes(in));
277            fail("should have failed to decode. Actually received: " +
278                    (result == null ? result : Arrays.toString(bytesToInts(result))));
279        } catch (ArrayIndexOutOfBoundsException e) {
280        }
281    }
282
283    private static void assertArrayEquals(int[] expected, int[] actual) {
284        assertSubArrayEquals(expected, expected.length, actual);
285    }
286
287    /** Assert that actual equals the first len bytes of expected. */
288    private static void assertSubArrayEquals(int[] expected, int len, int[] actual) {
289        // Convert the arrays to Strings for easy comparison / reporting.
290        String expectedString = intsToString(expected, len);
291        String actualString = intsToString(actual, actual.length);
292        assertEquals(expectedString, actualString);
293    }
294
295    private static String intsToString(int[] toConvert, int length) {
296        String[] out = new String[length];
297        for (int i = 0; i < length; i++) {
298            out[i] = "0x" + Integer.toHexString(toConvert[i]);
299        }
300        return Arrays.toString(out);
301    }
302}
303
304