blob: 6cfe1861641f4accd5f90130a3bc2a0b4547eac6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.io;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
public final class Base64Test extends TestCase {
public void testEncodeDecode() throws Exception {
assertEncodeDecode("");
assertEncodeDecode("Eg==", 0x12);
assertEncodeDecode("EjQ=", 0x12, 0x34);
assertEncodeDecode("EjRW", 0x12, 0x34, 0x56);
assertEncodeDecode("EjRWeA==", 0x12, 0x34, 0x56, 0x78);
assertEncodeDecode("EjRWeJo=", 0x12, 0x34, 0x56, 0x78, 0x9A);
assertEncodeDecode("EjRWeJq8", 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc);
}
public void testEncode_doesNotWrap() throws Exception {
int[] data = new int[61];
Arrays.fill(data, 0xff);
String expected = "///////////////////////////////////////////////////////////////////////"
+ "//////////w=="; // 84 chars
assertEncodeDecode(expected, data);
}
private static void assertEncodeDecode(String expectedEncoded, int... toEncode)
throws Exception {
// We should never expect (or receive) non-ASCII text from Base64.encoder.
asciiToBytes(expectedEncoded);
// Convert the convenient ints to the bytes we need.
byte[] inputBytes = new byte[toEncode.length];
for (int i = 0; i < toEncode.length; i++) {
inputBytes[i] = (byte) toEncode[i];
}
String encoded = Base64.encode(inputBytes);
assertEquals(expectedEncoded, encoded);
// Check we can round-trip the encoded bytes to
// arrive at what we started with.
int[] actualDecodedBytes = decodeToInts(encoded);
assertArrayEquals(toEncode, actualDecodedBytes);
}
public void testDecode_empty() throws Exception {
byte[] decoded = Base64.decode(new byte[0]);
assertEquals(0, decoded.length);
}
public void testDecode_truncated() throws Exception {
// Correct data, for reference.
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk"));
// The following are missing the final bytes
assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcmx"));
assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcm"));
assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvc"));
assertEquals("hello, wo", decodeToString("aGVsbG8sIHdv"));
}
public void testDecode_extraChars() throws Exception {
// Characters outside of alphabet before padding.
assertEquals("hello, world", decodeToString(" aGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGV sbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk "));
assertEquals(null, decodeToString("*aGVsbG8sIHdvcmxk"));
assertEquals(null, decodeToString("aGV*sbG8sIHdvcmxk"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk*"));
assertEquals("hello, world", decodeToString("\r\naGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGV\r\nsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\r\n"));
assertEquals("hello, world", decodeToString("\naGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGV\nsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\n"));
// padding 0
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk"));
// Extra padding
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk="));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk=="));
// Characters outside alphabet intermixed with (too much) padding.
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk ="));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk = = "));
// padding 1
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE="));
// Missing padding
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkPyE"));
// Characters outside alphabet before padding.
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE ="));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE*="));
// Trailing characters, otherwise valid.
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= "));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=*"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=X"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XY"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZ"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZA"));
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\n"));
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\r\n"));
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= "));
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=="));
// Whitespace characters outside alphabet intermixed with (too much) padding.
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE =="));
assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE = = "));
// padding 2
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg=="));
// Missing padding
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg"));
// Partially missing padding
assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg="));
// Characters outside alphabet before padding.
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg =="));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg*=="));
// Trailing characters, otherwise valid.
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== "));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==*"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==X"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XY"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZ"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZA"));
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\n"));
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\r\n"));
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== "));
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==="));
// Characters outside alphabet inside padding.
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg= ="));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=*="));
assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg=\r\n="));
// Characters inside alphabet inside padding.
assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=X="));
// Table 1 chars
assertEquals(null, decodeToString("_aGVsbG8sIHdvcmx"));
assertEquals(null, decodeToString("aGV_sbG8sIHdvcmx"));
assertEquals(null, decodeToString("aGVsbG8sIHdvcmx_"));
// Table 2 chars.
assertArrayEquals(
new int[] {0xfd, 0xa1, 0x95, 0xb1, 0xb1, 0xbc, 0xb0, 0x81, 0xdd, 0xbd, 0xc9,
0xb1 },
decodeToInts("/aGVsbG8sIHdvcmx"));
assertArrayEquals(
new int[] { 0x68, 0x65, 0x7f, 0xb1, 0xb1, 0xbc, 0xb0, 0x81, 0xdd, 0xbd, 0xc9,
0xb1 },
decodeToInts("aGV/sbG8sIHdvcmx"));
assertArrayEquals(
new int[] { 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 0x7f },
decodeToInts("aGVsbG8sIHdvcmx/"));
}
private static final int[] BYTE_VALUES = {
0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77
};
public void testDecode_nonAsciiBytes() throws Exception {
assertSubArrayEquals(BYTE_VALUES, 0, decodeToInts(""));
assertSubArrayEquals(BYTE_VALUES, 1, decodeToInts("/w=="));
assertSubArrayEquals(BYTE_VALUES, 2, decodeToInts("/+4="));
assertSubArrayEquals(BYTE_VALUES, 3, decodeToInts("/+7d"));
assertSubArrayEquals(BYTE_VALUES, 4, decodeToInts("/+7dzA=="));
assertSubArrayEquals(BYTE_VALUES, 5, decodeToInts("/+7dzLs="));
assertSubArrayEquals(BYTE_VALUES, 6, decodeToInts("/+7dzLuq"));
assertSubArrayEquals(BYTE_VALUES, 7, decodeToInts("/+7dzLuqmQ=="));
assertSubArrayEquals(BYTE_VALUES, 8, decodeToInts("/+7dzLuqmYg="));
}
public void testDecode_urlAlphabet() throws Exception {
assertNull(decodeToInts("_w=="));
assertNull(decodeToInts("-w=="));
}
/**
* Convenience function for decoding from a Base64 ASCII String to an ASCII String. A String is
* used for the output to make the tests compact. Can return null if the decoder returns null.
* If any of the strings involved are non-ASCII an exception is thrown.
* Use {@link #decodeToInts(String)} for decode tests that produce bytes
* outside of the ASCII range.
*/
private static String decodeToString(String in) throws Exception {
byte[] bytes = asciiToBytes(in);
byte[] out = Base64.decode(bytes);
if (out == null) {
return null;
}
return bytesToAscii(out);
}
private static String bytesToAscii(byte[] bytes) {
try {
CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
ByteBuffer bytesBuffer = ByteBuffer.wrap(bytes);
CharBuffer charsBuffer = decoder.decode(bytesBuffer);
char[] chars = new char[charsBuffer.remaining()];
charsBuffer.get(chars, 0, chars.length);
return new String(chars);
} catch (CharacterCodingException e) {
// Use bytes in your test, not Strings.
throw new AssertionFailedError("Cannot convert test bytes to String safely: " +
Arrays.toString(bytesToInts(bytes)) + " contains non-ASCII codes");
}
}
private static byte[] asciiToBytes(String string) {
try {
char[] chars = string.toCharArray();
CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder();
encoder.onMalformedInput(CodingErrorAction.REPORT);
encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
CharBuffer charsBuffer = CharBuffer.wrap(chars);
ByteBuffer bytesBuffer = encoder.encode(charsBuffer);
byte[] bytes = new byte[bytesBuffer.remaining()];
bytesBuffer.get(bytes, 0, bytes.length);
return bytes;
} catch (CharacterCodingException e) {
// Use bytes in your test, not Strings.
throw new AssertionFailedError("Cannot convert test String to bytes safely: " + string +
" contains non-ASCII characters");
}
}
/** Decodes an ASCII string, returning an int array. */
private static int[] decodeToInts(String in) throws Exception {
byte[] bytes = Base64.decode(asciiToBytes(in));
return bytesToInts(bytes);
}
/**
* Convert a byte[] to an int[]. int is used because it is more convenient to use ints in
* tests.
*/
private static int[] bytesToInts(byte[] bytes) {
if (bytes == null) {
return null;
}
int[] ints = new int[bytes.length];
for (int i = 0; i < bytes.length; i++) {
ints[i] = bytes[i] & 0xff;
}
return ints;
}
/** Assert that decoding 'in' throws ArrayIndexOutOfBoundsException. */
private static void assertDecodeBad(String in) throws Exception {
try {
byte[] result = Base64.decode(asciiToBytes(in));
fail("should have failed to decode. Actually received: " +
(result == null ? result : Arrays.toString(bytesToInts(result))));
} catch (ArrayIndexOutOfBoundsException e) {
}
}
private static void assertArrayEquals(int[] expected, int[] actual) {
assertSubArrayEquals(expected, expected.length, actual);
}
/** Assert that actual equals the first len bytes of expected. */
private static void assertSubArrayEquals(int[] expected, int len, int[] actual) {
// Convert the arrays to Strings for easy comparison / reporting.
String expectedString = intsToString(expected, len);
String actualString = intsToString(actual, actual.length);
assertEquals(expectedString, actualString);
}
private static String intsToString(int[] toConvert, int length) {
String[] out = new String[length];
for (int i = 0; i < length; i++) {
out[i] = "0x" + Integer.toHexString(toConvert[i]);
}
return Arrays.toString(out);
}
}