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