/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed 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.java.util;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.Arrays.copyOfRange;
public class Base64Test extends TestCase {
/**
* The base 64 alphabet from RFC 4648 Table 1.
*/
private static final Set<Character> TABLE_1 =
Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
)));
/**
* The "URL and Filename safe" Base 64 Alphabet from RFC 4648 Table 2.
*/
private static final Set<Character> TABLE_2 =
Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
)));
public void testAlphabet_plain() {
checkAlphabet(TABLE_1, "", Base64.getEncoder());
}
public void testAlphabet_mime() {
checkAlphabet(TABLE_1, "\r\n", Base64.getMimeEncoder());
}
public void testAlphabet_url() {
checkAlphabet(TABLE_2, "", Base64.getUrlEncoder());
}
private static void checkAlphabet(Set<Character> expectedAlphabet, String lineSeparator,
Encoder encoder) {
assertEquals("Base64 alphabet size must be 64 characters", 64, expectedAlphabet.size());
byte[] bytes = new byte[256];
for (int i = 0; i < 256; i++) {
bytes[i] = (byte) i;
}
Set<Character> actualAlphabet = new HashSet<>();
byte[] encodedBytes = encoder.encode(bytes);
// ignore the padding
int endIndex = encodedBytes.length;
while (endIndex > 0 && encodedBytes[endIndex - 1] == '=') {
endIndex--;
}
for (byte b : Arrays.copyOfRange(encodedBytes, 0, endIndex)) {
char c = (char) b;
actualAlphabet.add(c);
}
for (char c : lineSeparator.toCharArray()) {
assertTrue(actualAlphabet.remove(c));
}
assertEquals(expectedAlphabet, actualAlphabet);
}
/**
* Checks decoding of bytes containing a value outside of the allowed
* {@link #TABLE_1 "basic" alphabet}.
*/
public void testDecoder_extraChars_basic() throws Exception {
Decoder basicDecoder = Base64.getDecoder(); // uses Table 1
// Check failure cases common to both RFC4648 Table 1 and Table 2 decoding.
checkDecoder_extraChars_common(basicDecoder);
// Tests characters that are part of RFC4848 Table 2 but not Table 1.
assertDecodeThrowsIAe(basicDecoder, "_aGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(basicDecoder, "aGV_sbG8sIHdvcmx");
assertDecodeThrowsIAe(basicDecoder, "aGVsbG8sIHdvcmx_");
}
/**
* Checks decoding of bytes containing a value outside of the allowed
* {@link #TABLE_2 url alphabet}.
*/
public void testDecoder_extraChars_url() throws Exception {
Decoder urlDecoder = Base64.getUrlDecoder(); // uses Table 2
// Check failure cases common to both RFC4648 table 1 and table 2 decoding.
checkDecoder_extraChars_common(urlDecoder);
// Tests characters that are part of RFC4848 Table 1 but not Table 2.
assertDecodeThrowsIAe(urlDecoder, "/aGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(urlDecoder, "aGV/sbG8sIHdvcmx");
assertDecodeThrowsIAe(urlDecoder, "aGVsbG8sIHdvcmx/");
}
/**
* Checks characters that are bad both in RFC4648 {@link #TABLE_1} and
* in {@link #TABLE_2} based decoding.
*/
private static void checkDecoder_extraChars_common(Decoder decoder) throws Exception {
// Characters outside alphabet before padding.
assertDecodeThrowsIAe(decoder, " aGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGV sbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx ");
assertDecodeThrowsIAe(decoder, "*aGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGV*sbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx*");
assertDecodeThrowsIAe(decoder, "\r\naGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGV\r\nsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx\r\n");
assertDecodeThrowsIAe(decoder, "\naGVsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGV\nsbG8sIHdvcmx");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmx\n");
// padding 0
assertEquals("hello, world", decodeToAscii(decoder, "aGVsbG8sIHdvcmxk"));
// Extra padding
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk=");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk==");
// Characters outside alphabet intermixed with (too much) padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk =");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxk = = ");
// padding 1
assertEquals("hello, world?!", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkPyE="));
// Missing padding
assertEquals("hello, world?!", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkPyE"));
// Characters outside alphabet before padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE =");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE*=");
// Trailing characters, otherwise valid.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE= ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=*");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=X");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XY");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XYZ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=XYZA");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=\n");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE=\r\n");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE= ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE==");
// Characters outside alphabet intermixed with (too much) padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE ==");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkPyE = = ");
// padding 2
assertEquals("hello, world.", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkLg=="));
// Missing padding
assertEquals("hello, world.", decodeToAscii(decoder, "aGVsbG8sIHdvcmxkLg"));
// Partially missing padding
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=");
// Characters outside alphabet before padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg ==");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg*==");
// Trailing characters, otherwise valid.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg== ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==*");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==X");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XY");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XYZ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==XYZA");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==\n");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg==\r\n");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg== ");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg===");
// Characters outside alphabet inside padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg= =");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=*=");
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=\r\n=");
// Characters inside alphabet inside padding.
assertDecodeThrowsIAe(decoder, "aGVsbG8sIHdvcmxkLg=X=");
}
public void testDecoder_extraChars_mime() throws Exception {
Decoder mimeDecoder = Base64.getMimeDecoder();
// Characters outside alphabet before padding.
assertEquals("hello, world", decodeToAscii(mimeDecoder, " aGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV sbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk "));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "_aGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV_sbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk_"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "*aGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV*sbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk*"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "\r\naGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV\r\nsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk\r\n"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "\naGVsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGV\nsbG8sIHdvcmxk"));
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk\n"));
// padding 0
assertEquals("hello, world", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxk"));
// Extra padding
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk=");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk==");
// Characters outside alphabet intermixed with (too much) padding.
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk =");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxk = = ");
// padding 1
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE="));
// Missing padding
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE"));
// Characters outside alphabet before padding.
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE ="));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE*="));
// Trailing characters, otherwise valid.
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE= "));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=*"));
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=X");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XY");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XYZ");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=XYZA");
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=\n"));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=\r\n"));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE= "));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE=="));
// Characters outside alphabet intermixed with (too much) padding.
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE =="));
assertEquals("hello, world?!", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkPyE = = "));
// padding 2
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg=="));
// Missing padding
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg"));
// Partially missing padding
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=");
// Characters outside alphabet before padding.
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg =="));
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg*=="));
// Trailing characters, otherwise valid.
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg== "));
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==*"));
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==X");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XY");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XYZ");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg==XYZA");
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==\n"));
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==\r\n"));
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg== "));
assertEquals("hello, world.", decodeToAscii(mimeDecoder, "aGVsbG8sIHdvcmxkLg==="));
// Characters outside alphabet inside padding are not allowed by the MIME decoder.
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg= =");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=*=");
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=\r\n=");
// Characters inside alphabet inside padding.
assertDecodeThrowsIAe(mimeDecoder, "aGVsbG8sIHdvcmxkLg=X=");
}
public void testDecoder_nonPrintableBytes_basic() throws Exception {
checkDecoder_nonPrintableBytes_table1(Base64.getDecoder());
}
public void testDecoder_nonPrintableBytes_mime() throws Exception {
checkDecoder_nonPrintableBytes_table1(Base64.getMimeDecoder());
}
/**
* Check decoding sample non-ASCII byte[] values from a {@link #TABLE_1}
* encoded String.
*/
private static void checkDecoder_nonPrintableBytes_table1(Decoder decoder) throws Exception {
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 0, decoder.decode(""));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 1, decoder.decode("/w=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 2, decoder.decode("/+4="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 3, decoder.decode("/+7d"));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 4, decoder.decode("/+7dzA=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 5, decoder.decode("/+7dzLs="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 6, decoder.decode("/+7dzLuq"));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 7, decoder.decode("/+7dzLuqmQ=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 8, decoder.decode("/+7dzLuqmYg="));
}
/**
* Check decoding sample non-ASCII byte[] values from a {@link #TABLE_2}
* (url safe) encoded String.
*/
public void testDecoder_nonPrintableBytes_url() throws Exception {
Decoder decoder = Base64.getUrlDecoder();
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 0, decoder.decode(""));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 1, decoder.decode("_w=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 2, decoder.decode("_-4="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 3, decoder.decode("_-7d"));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 4, decoder.decode("_-7dzA=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 5, decoder.decode("_-7dzLs="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 6, decoder.decode("_-7dzLuq"));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 7, decoder.decode("_-7dzLuqmQ=="));
assertArrayPrefixEquals(SAMPLE_NON_ASCII_BYTES, 8, decoder.decode("_-7dzLuqmYg="));
}
private static final byte[] SAMPLE_NON_ASCII_BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
(byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
(byte) 0x99, (byte) 0x88, (byte) 0x77 };
public void testDecoder_closedStream() {
try {
closedDecodeStream().available();
fail("Should have thrown");
} catch (IOException expected) {
}
try {
closedDecodeStream().read();
fail("Should have thrown");
} catch (IOException expected) {
}
try {
closedDecodeStream().read(new byte[23]);
fail("Should have thrown");
} catch (IOException expected) {
}
try {
closedDecodeStream().read(new byte[23], 0, 1);
fail("Should have thrown");
} catch (IOException expected) {
}
}
private static InputStream closedDecodeStream() {
InputStream result = Base64.getDecoder().wrap(new ByteArrayInputStream(new byte[0]));
try {
result.close();
} catch (IOException e) {
fail(e.getMessage());
}
return result;
}
/**
* Tests {@link Decoder#decode(byte[], byte[])} for correctness as well as
* for consistency with other methods tested elsewhere.
*/
public void testDecoder_decodeArrayToArray() {
Decoder decoder = Base64.getDecoder();
// Empty input
assertEquals(0, decoder.decode(new byte[0], new byte[0]));
// Test data for non-empty input
String inputString = "YWJjZWZnaGk=";
byte[] input = inputString.getBytes(US_ASCII);
String expectedString = "abcefghi";
byte[] decodedBytes = expectedString.getBytes(US_ASCII);
// check test data consistency with other methods that are tested elsewhere
assertRoundTrip(Base64.getEncoder(), decoder, inputString, decodedBytes);
// Non-empty input: output array too short
byte[] tooShort = new byte[decodedBytes.length - 1];
try {
decoder.decode(input, tooShort);
fail();
} catch (IllegalArgumentException expected) {
}
// Non-empty input: output array longer than required
byte[] tooLong = new byte[decodedBytes.length + 1];
int tooLongBytesDecoded = decoder.decode(input, tooLong);
assertEquals(decodedBytes.length, tooLongBytesDecoded);
assertEquals(0, tooLong[tooLong.length - 1]);
assertArrayPrefixEquals(tooLong, decodedBytes.length, decodedBytes);
// Non-empty input: output array has exact minimum required size
byte[] justRight = new byte[decodedBytes.length];
int justRightBytesDecoded = decoder.decode(input, justRight);
assertEquals(decodedBytes.length, justRightBytesDecoded);
assertArrayEquals(decodedBytes, justRight);
}
public void testDecoder_decodeByteBuffer() {
Decoder decoder = Base64.getDecoder();
byte[] emptyByteArray = new byte[0];
ByteBuffer emptyByteBuffer = ByteBuffer.wrap(emptyByteArray);
ByteBuffer emptyDecodedBuffer = decoder.decode(emptyByteBuffer);
assertEquals(emptyByteBuffer, emptyDecodedBuffer);
assertNotSame(emptyByteArray, emptyDecodedBuffer);
// Test the two types of byte buffer.
String inputString = "YWJjZWZnaGk=";
byte[] input = inputString.getBytes(US_ASCII);
String expectedString = "abcefghi";
byte[] expectedBytes = expectedString.getBytes(US_ASCII);
ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
checkDecoder_decodeByteBuffer(decoder, inputBuffer, expectedBytes);
inputBuffer = ByteBuffer.allocateDirect(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
checkDecoder_decodeByteBuffer(decoder, inputBuffer, expectedBytes);
}
private static void checkDecoder_decodeByteBuffer(
Decoder decoder, ByteBuffer inputBuffer, byte[] expectedBytes) {
assertEquals(0, inputBuffer.position());
assertEquals(inputBuffer.remaining(), inputBuffer.limit());
int inputLength = inputBuffer.remaining();
ByteBuffer decodedBuffer = decoder.decode(inputBuffer);
assertEquals(inputLength, inputBuffer.position());
assertEquals(0, inputBuffer.remaining());
assertEquals(inputLength, inputBuffer.limit());
assertEquals(0, decodedBuffer.position());
assertEquals(expectedBytes.length, decodedBuffer.remaining());
assertEquals(expectedBytes.length, decodedBuffer.limit());
}
public void testDecoder_decodeByteBuffer_invalidData() {
Decoder decoder = Base64.getDecoder();
// Test the two types of byte buffer.
String inputString = "AAAA AAAA";
byte[] input = inputString.getBytes(US_ASCII);
ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
checkDecoder_decodeByteBuffer_invalidData(decoder, inputBuffer);
inputBuffer = ByteBuffer.allocateDirect(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
checkDecoder_decodeByteBuffer_invalidData(decoder, inputBuffer);
}
private static void checkDecoder_decodeByteBuffer_invalidData(
Decoder decoder, ByteBuffer inputBuffer) {
assertEquals(0, inputBuffer.position());
assertEquals(inputBuffer.remaining(), inputBuffer.limit());
int limit = inputBuffer.limit();
try {
decoder.decode(inputBuffer);
fail();
} catch (IllegalArgumentException expected) {
}
assertEquals(0, inputBuffer.position());
assertEquals(limit, inputBuffer.remaining());
assertEquals(limit, inputBuffer.limit());
}
public void testDecoder_nullArgs() {
checkDecoder_nullArgs(Base64.getDecoder());
checkDecoder_nullArgs(Base64.getMimeDecoder());
checkDecoder_nullArgs(Base64.getUrlDecoder());
}
private static void checkDecoder_nullArgs(Decoder decoder) {
assertThrowsNpe(() -> decoder.decode((byte[]) null));
assertThrowsNpe(() -> decoder.decode((String) null));
assertThrowsNpe(() -> decoder.decode(null, null));
assertThrowsNpe(() -> decoder.decode((ByteBuffer) null));
assertThrowsNpe(() -> decoder.wrap(null));
}
public void testEncoder_nullArgs() {
checkEncoder_nullArgs(Base64.getEncoder());
checkEncoder_nullArgs(Base64.getMimeEncoder());
checkEncoder_nullArgs(Base64.getUrlEncoder());
checkEncoder_nullArgs(Base64.getMimeEncoder(20, new byte[] { '*' }));
checkEncoder_nullArgs(Base64.getEncoder().withoutPadding());
checkEncoder_nullArgs(Base64.getMimeEncoder().withoutPadding());
checkEncoder_nullArgs(Base64.getUrlEncoder().withoutPadding());
checkEncoder_nullArgs(Base64.getMimeEncoder(20, new byte[] { '*' }).withoutPadding());
}
private static void checkEncoder_nullArgs(Encoder encoder) {
assertThrowsNpe(() -> encoder.encode((byte[]) null));
assertThrowsNpe(() -> encoder.encodeToString(null));
assertThrowsNpe(() -> encoder.encode(null, null));
assertThrowsNpe(() -> encoder.encode((ByteBuffer) null));
assertThrowsNpe(() -> encoder.wrap(null));
}
public void testEncoder_nonPrintableBytes() throws Exception {
Encoder encoder = Base64.getUrlEncoder();
assertEquals("", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 0)));
assertEquals("_w==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 1)));
assertEquals("_-4=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 2)));
assertEquals("_-7d", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 3)));
assertEquals("_-7dzA==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 4)));
assertEquals("_-7dzLs=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 5)));
assertEquals("_-7dzLuq", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 6)));
assertEquals("_-7dzLuqmQ==", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 7)));
assertEquals("_-7dzLuqmYg=", encoder.encodeToString(copyOfRange(SAMPLE_NON_ASCII_BYTES, 0, 8)));
}
public void testEncoder_lineLength() throws Exception {
String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
String in_57 = in_56 + "e";
String in_58 = in_56 + "ef";
String in_59 = in_56 + "efg";
String in_60 = in_56 + "efgh";
String in_61 = in_56 + "efghi";
String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
String out_56 = prefix + "Y2Q=";
String out_57 = prefix + "Y2Rl";
String out_58 = prefix + "Y2Rl\r\nZg==";
String out_59 = prefix + "Y2Rl\r\nZmc=";
String out_60 = prefix + "Y2Rl\r\nZmdo";
String out_61 = prefix + "Y2Rl\r\nZmdoaQ==";
Encoder encoder = Base64.getMimeEncoder();
Decoder decoder = Base64.getMimeDecoder();
assertEquals("", encodeFromAscii(encoder, decoder, ""));
assertEquals(out_56, encodeFromAscii(encoder, decoder, in_56));
assertEquals(out_57, encodeFromAscii(encoder, decoder, in_57));
assertEquals(out_58, encodeFromAscii(encoder, decoder, in_58));
assertEquals(out_59, encodeFromAscii(encoder, decoder, in_59));
assertEquals(out_60, encodeFromAscii(encoder, decoder, in_60));
assertEquals(out_61, encodeFromAscii(encoder, decoder, in_61));
encoder = Base64.getUrlEncoder();
decoder = Base64.getUrlDecoder();
assertEquals(out_56.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_56));
assertEquals(out_57.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_57));
assertEquals(out_58.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_58));
assertEquals(out_59.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_59));
assertEquals(out_60.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_60));
assertEquals(out_61.replaceAll("\r\n", ""), encodeFromAscii(encoder, decoder, in_61));
}
public void testGetMimeEncoder_invalidLineSeparator() {
byte[] invalidLineSeparator = { 'A' };
try {
Base64.getMimeEncoder(20, invalidLineSeparator);
fail();
} catch (IllegalArgumentException expected) {
}
try {
Base64.getMimeEncoder(0, invalidLineSeparator);
fail();
} catch (IllegalArgumentException expected) {
}
try {
Base64.getMimeEncoder(20, null);
fail();
} catch (NullPointerException expected) {
}
try {
Base64.getMimeEncoder(0, null);
fail();
} catch (NullPointerException expected) {
}
}
public void testEncoder_closedStream() {
try {
closedEncodeStream().write(100);
fail("Should have thrown");
} catch (IOException expected) {
}
try {
closedEncodeStream().write(new byte[100]);
fail("Should have thrown");
} catch (IOException expected) {
}
try {
closedEncodeStream().write(new byte[100], 0, 1);
fail("Should have thrown");
} catch (IOException expected) {
}
}
private static OutputStream closedEncodeStream() {
OutputStream result = Base64.getEncoder().wrap(new ByteArrayOutputStream());
try {
result.close();
} catch (IOException e) {
fail(e.getMessage());
}
return result;
}
/**
* Tests {@link Decoder#decode(byte[], byte[])} for correctness.
*/
public void testEncoder_encodeArrayToArray() {
Encoder encoder = Base64.getEncoder();
// Empty input
assertEquals(0, encoder.encode(new byte[0], new byte[0]));
// Test data for non-empty input
byte[] input = "abcefghi".getBytes(US_ASCII);
String expectedString = "YWJjZWZnaGk=";
byte[] encodedBytes = expectedString.getBytes(US_ASCII);
// Non-empty input: output array too short
byte[] tooShort = new byte[encodedBytes.length - 1];
try {
encoder.encode(input, tooShort);
fail();
} catch (IllegalArgumentException expected) {
}
// Non-empty input: output array longer than required
byte[] tooLong = new byte[encodedBytes.length + 1];
int tooLongBytesEncoded = encoder.encode(input, tooLong);
assertEquals(encodedBytes.length, tooLongBytesEncoded);
assertEquals(0, tooLong[tooLong.length - 1]);
assertArrayPrefixEquals(tooLong, encodedBytes.length, encodedBytes);
// Non-empty input: output array has exact minimum required size
byte[] justRight = new byte[encodedBytes.length];
int justRightBytesEncoded = encoder.encode(input, justRight);
assertEquals(encodedBytes.length, justRightBytesEncoded);
assertArrayEquals(encodedBytes, justRight);
}
public void testEncoder_encodeByteBuffer() {
Encoder encoder = Base64.getEncoder();
byte[] emptyByteArray = new byte[0];
ByteBuffer emptyByteBuffer = ByteBuffer.wrap(emptyByteArray);
ByteBuffer emptyEncodedBuffer = encoder.encode(emptyByteBuffer);
assertEquals(emptyByteBuffer, emptyEncodedBuffer);
assertNotSame(emptyByteArray, emptyEncodedBuffer);
// Test the two types of byte buffer.
byte[] input = "abcefghi".getBytes(US_ASCII);
String expectedString = "YWJjZWZnaGk=";
byte[] expectedBytes = expectedString.getBytes(US_ASCII);
ByteBuffer inputBuffer = ByteBuffer.allocate(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
testEncoder_encodeByteBuffer(encoder, inputBuffer, expectedBytes);
inputBuffer = ByteBuffer.allocateDirect(input.length);
inputBuffer.put(input);
inputBuffer.position(0);
testEncoder_encodeByteBuffer(encoder, inputBuffer, expectedBytes);
}
private static void testEncoder_encodeByteBuffer(
Encoder encoder, ByteBuffer inputBuffer, byte[] expectedBytes) {
assertEquals(0, inputBuffer.position());
assertEquals(inputBuffer.remaining(), inputBuffer.limit());
int inputLength = inputBuffer.remaining();
ByteBuffer encodedBuffer = encoder.encode(inputBuffer);
assertEquals(inputLength, inputBuffer.position());
assertEquals(0, inputBuffer.remaining());
assertEquals(inputLength, inputBuffer.limit());
assertEquals(0, encodedBuffer.position());
assertEquals(expectedBytes.length, encodedBuffer.remaining());
assertEquals(expectedBytes.length, encodedBuffer.limit());
}
/**
* Checks that all encoders/decoders map {@code new byte[0]} to "" and vice versa.
*/
public void testRoundTrip_empty() {
checkRoundTrip_empty(Base64.getEncoder(), Base64.getDecoder());
checkRoundTrip_empty(Base64.getMimeEncoder(), Base64.getMimeDecoder());
byte[] sep = new byte[] { '\r', '\n' };
checkRoundTrip_empty(Base64.getMimeEncoder(-1, sep), Base64.getMimeDecoder());
checkRoundTrip_empty(Base64.getMimeEncoder(20, new byte[0]), Base64.getMimeDecoder());
checkRoundTrip_empty(Base64.getMimeEncoder(23, sep), Base64.getMimeDecoder());
checkRoundTrip_empty(Base64.getMimeEncoder(76, sep), Base64.getMimeDecoder());
checkRoundTrip_empty(Base64.getUrlEncoder(), Base64.getUrlDecoder());
}
private static void checkRoundTrip_empty(Encoder encoder, Decoder decoder) {
assertRoundTrip(encoder, decoder, "", new byte[0]);
}
/**
* Encoding of byte values 0..255 using the non-URL alphabet.
*/
private static final String ALL_BYTE_VALUES_ENCODED =
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4" +
"OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx" +
"cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq" +
"q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj" +
"5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
public void testRoundTrip_allBytes_plain() {
checkRoundTrip_allBytes_singleLine(Base64.getEncoder(), Base64.getDecoder());
}
/**
* Checks that if the lineSeparator is empty or the line length is {@code <= 3}
* or larger than the data to be encoded, a single line is returned.
*/
public void testRoundTrip_allBytes_mime_singleLine() {
Decoder decoder = Base64.getMimeDecoder();
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(76, new byte[0]), decoder);
// Line lengths <= 3 mean no wrapping; the separator is ignored in that case.
byte[] separator = new byte[] { '*' };
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(Integer.MIN_VALUE, separator),
decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(-1, separator), decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(0, separator), decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(1, separator), decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(2, separator), decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(3, separator), decoder);
// output fits into the permitted line length
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(
ALL_BYTE_VALUES_ENCODED.length(), separator), decoder);
checkRoundTrip_allBytes_singleLine(Base64.getMimeEncoder(Integer.MAX_VALUE, separator),
decoder);
}
/**
* Checks round-trip encoding/decoding for a few simple examples that
* should work the same across three Encoder/Decoder pairs: This is
* because they only use characters that are in both RFC 4648 Table 1
* and Table 2, and are short enough to fit into a single line.
*/
public void testRoundTrip_simple_basic() throws Exception {
// uses Table 1, never adds linebreaks
checkRoundTrip_simple(Base64.getEncoder(), Base64.getDecoder());
// uses Table 1, allows 76 chars in a line
checkRoundTrip_simple(Base64.getMimeEncoder(), Base64.getMimeDecoder());
// uses Table 2, never adds linebreaks
checkRoundTrip_simple(Base64.getUrlEncoder(), Base64.getUrlDecoder());
}
private static void checkRoundTrip_simple(Encoder encoder, Decoder decoder) throws Exception {
assertRoundTrip(encoder, decoder, "YQ==", "a".getBytes(US_ASCII));
assertRoundTrip(encoder, decoder, "YWI=", "ab".getBytes(US_ASCII));
assertRoundTrip(encoder, decoder, "YWJj", "abc".getBytes(US_ASCII));
assertRoundTrip(encoder, decoder, "YWJjZA==", "abcd".getBytes(US_ASCII));
}
/** check a range of possible line lengths */
public void testRoundTrip_allBytes_mime_lineLength() {
Decoder decoder = Base64.getMimeDecoder();
byte[] separator = new byte[] { '*' };
checkRoundTrip_allBytes(Base64.getMimeEncoder(4, separator), decoder,
wrapLines("*", ALL_BYTE_VALUES_ENCODED, 4));
checkRoundTrip_allBytes(Base64.getMimeEncoder(8, separator), decoder,
wrapLines("*", ALL_BYTE_VALUES_ENCODED, 8));
checkRoundTrip_allBytes(Base64.getMimeEncoder(20, separator), decoder,
wrapLines("*", ALL_BYTE_VALUES_ENCODED, 20));
checkRoundTrip_allBytes(Base64.getMimeEncoder(100, separator), decoder,
wrapLines("*", ALL_BYTE_VALUES_ENCODED, 100));
checkRoundTrip_allBytes(Base64.getMimeEncoder(Integer.MAX_VALUE & ~3, separator), decoder,
wrapLines("*", ALL_BYTE_VALUES_ENCODED, Integer.MAX_VALUE & ~3));
}
public void testRoundTrip_allBytes_mime_lineLength_defaultsTo76Chars() {
checkRoundTrip_allBytes(Base64.getMimeEncoder(), Base64.getMimeDecoder(),
wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 76));
}
/**
* checks that the specified line length is rounded down to the nearest multiple of 4.
*/
public void testRoundTrip_allBytes_mime_lineLength_isRoundedDown() {
Decoder decoder = Base64.getMimeDecoder();
byte[] separator = new byte[] { '\r', '\n' };
checkRoundTrip_allBytes(Base64.getMimeEncoder(60, separator), decoder,
wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 60));
checkRoundTrip_allBytes(Base64.getMimeEncoder(63, separator), decoder,
wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 60));
checkRoundTrip_allBytes(Base64.getMimeEncoder(10, separator), decoder,
wrapLines("\r\n", ALL_BYTE_VALUES_ENCODED, 8));
}
public void testRoundTrip_allBytes_url() {
String encodedUrl = ALL_BYTE_VALUES_ENCODED.replace('+', '-').replace('/', '_');
checkRoundTrip_allBytes(Base64.getUrlEncoder(), Base64.getUrlDecoder(), encodedUrl);
}
/**
* Checks round-trip encoding/decoding of all byte values 0..255 for
* the case where the Encoder doesn't add any linebreaks.
*/
private static void checkRoundTrip_allBytes_singleLine(Encoder encoder, Decoder decoder) {
checkRoundTrip_allBytes(encoder, decoder, ALL_BYTE_VALUES_ENCODED);
}
/**
* Checks that byte values 0..255, in order, are encoded to exactly
* the given String (including any linebreaks, if present) and that
* that String can be decoded back to the same byte values.
*
* @param encoded the expected encoded representation of the (unsigned)
* byte values 0..255, in order.
*/
private static void checkRoundTrip_allBytes(Encoder encoder, Decoder decoder, String encoded) {
byte[] bytes = new byte[256];
for (int i = 0; i < 256; i++) {
bytes[i] = (byte) i;
}
assertRoundTrip(encoder, decoder, encoded, bytes);
}
public void testRoundTrip_variousSizes_plain() {
checkRoundTrip_variousSizes(Base64.getEncoder(), Base64.getDecoder());
}
public void testRoundTrip_variousSizes_mime() {
checkRoundTrip_variousSizes(Base64.getMimeEncoder(), Base64.getMimeDecoder());
}
public void testRoundTrip_variousSizes_url() {
checkRoundTrip_variousSizes(Base64.getUrlEncoder(), Base64.getUrlDecoder());
}
/**
* Checks that various-sized inputs survive a round trip.
*/
private static void checkRoundTrip_variousSizes(Encoder encoder, Decoder decoder) {
Random random = new Random(7654321);
for (int numBytes : new int [] { 0, 1, 2, 75, 76, 77, 80, 100, 1234 }) {
byte[] bytes = new byte[numBytes];
random.nextBytes(bytes);
byte[] result = decoder.decode(encoder.encode(bytes));
assertArrayEquals(bytes, result);
}
}
public void testRoundtrip_wrap_basic() throws Exception {
Encoder encoder = Base64.getEncoder();
Decoder decoder = Base64.getDecoder();
checkRoundTrip_wrapInputStream(encoder, decoder);
}
public void testRoundtrip_wrap_mime() throws Exception {
Encoder encoder = Base64.getMimeEncoder();
Decoder decoder = Base64.getMimeDecoder();
checkRoundTrip_wrapInputStream(encoder, decoder);
}
public void testRoundTrip_wrap_url() throws Exception {
Encoder encoder = Base64.getUrlEncoder();
Decoder decoder = Base64.getUrlDecoder();
checkRoundTrip_wrapInputStream(encoder, decoder);
}
/**
* Checks that the {@link Decoder#wrap(InputStream) wrapping} an
* InputStream of encoded data yields the plain data that was
* previously {@link Encoder#encode(byte[]) encoded}.
*/
private static void checkRoundTrip_wrapInputStream(Encoder encoder, Decoder decoder)
throws IOException {
Random random = new Random(32176L);
int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
// Test input needs to be at least 2048 bytes to fill up the
// read buffer of Base64InputStream.
byte[] plain = new byte[4567];
random.nextBytes(plain);
byte[] encoded = encoder.encode(plain);
byte[] actual = new byte[plain.length * 2];
int b;
// ----- test decoding ("encoded" -> "plain") -----
// read as much as it will give us in one chunk
ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
InputStream b64is = decoder.wrap(bais);
int ap = 0;
while ((b = b64is.read(actual, ap, actual.length - ap)) != -1) {
ap += b;
}
assertArrayPrefixEquals(actual, ap, plain);
// read individual bytes
bais = new ByteArrayInputStream(encoded);
b64is = decoder.wrap(bais);
ap = 0;
while ((b = b64is.read()) != -1) {
actual[ap++] = (byte) b;
}
assertArrayPrefixEquals(actual, ap, plain);
// mix reads of variously-sized arrays with one-byte reads
bais = new ByteArrayInputStream(encoded);
b64is = decoder.wrap(bais);
ap = 0;
while (true) {
int l = writeLengths[random.nextInt(writeLengths.length)];
if (l >= 0) {
b = b64is.read(actual, ap, l);
if (b == -1) {
break;
}
ap += b;
} else {
for (int i = 0; i < -l; ++i) {
if ((b = b64is.read()) == -1) {
break;
}
actual[ap++] = (byte) b;
}
}
}
assertArrayPrefixEquals(actual, ap, plain);
}
public void testDecoder_wrap_singleByteReads() throws IOException {
InputStream in = Base64.getDecoder().wrap(new ByteArrayInputStream("/v8=".getBytes()));
assertEquals(254, in.read());
assertEquals(255, in.read());
assertEquals(-1, in.read());
}
public void testEncoder_withoutPadding() {
byte[] bytes = new byte[] { (byte) 0xFE, (byte) 0xFF };
assertEquals("/v8=", Base64.getEncoder().encodeToString(bytes));
assertEquals("/v8", Base64.getEncoder().withoutPadding().encodeToString(bytes));
assertEquals("/v8=", Base64.getMimeEncoder().encodeToString(bytes));
assertEquals("/v8", Base64.getMimeEncoder().withoutPadding().encodeToString(bytes));
assertEquals("_v8=", Base64.getUrlEncoder().encodeToString(bytes));
assertEquals("_v8", Base64.getUrlEncoder().withoutPadding().encodeToString(bytes));
}
public void testEncoder_wrap_plain() throws Exception {
checkWrapOutputStreamConsistentWithEncode(Base64.getEncoder());
}
public void testEncoder_wrap_url() throws Exception {
checkWrapOutputStreamConsistentWithEncode(Base64.getUrlEncoder());
}
public void testEncoder_wrap_mime() throws Exception {
checkWrapOutputStreamConsistentWithEncode(Base64.getMimeEncoder());
}
/** A way of writing bytes to an OutputStream. */
interface WriteStrategy {
void write(byte[] bytes, OutputStream out) throws IOException;
}
private static void checkWrapOutputStreamConsistentWithEncode(Encoder encoder)
throws Exception {
final Random random = new Random(32176L);
// one large write(byte[]) of the whole input
WriteStrategy allAtOnce = (bytes, out) -> out.write(bytes);
checkWrapOutputStreamConsistentWithEncode(encoder, allAtOnce);
// many calls to write(int)
WriteStrategy byteWise = (bytes, out) -> {
for (byte b : bytes) {
out.write(b);
}
};
checkWrapOutputStreamConsistentWithEncode(encoder, byteWise);
// intermixed sequences of write(int) with
// write(byte[],int,int) of various lengths.
WriteStrategy mixed = (bytes, out) -> {
int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
int p = 0;
while (p < bytes.length) {
int l = writeLengths[random.nextInt(writeLengths.length)];
l = Math.min(l, bytes.length - p);
if (l >= 0) {
out.write(bytes, p, l);
p += l;
} else {
l = Math.min(-l, bytes.length - p);
for (int i = 0; i < l; ++i) {
out.write(bytes[p + i]);
}
p += l;
}
}
};
checkWrapOutputStreamConsistentWithEncode(encoder, mixed);
}
/**
* Checks that writing to a wrap()ping OutputStream produces the same
* output on the wrapped stream as {@link Encoder#encode(byte[])}.
*/
private static void checkWrapOutputStreamConsistentWithEncode(Encoder encoder,
WriteStrategy writeStrategy) throws IOException {
Random random = new Random(32176L);
// Test input needs to be at least 1024 bytes to test filling
// up the write(int) buffer of Base64OutputStream.
byte[] plain = new byte[1234];
random.nextBytes(plain);
byte[] encodeResult = encoder.encode(plain);
ByteArrayOutputStream wrappedOutputStream = new ByteArrayOutputStream();
try (OutputStream plainOutputStream = encoder.wrap(wrappedOutputStream)) {
writeStrategy.write(plain, plainOutputStream);
}
assertArrayEquals(encodeResult, wrappedOutputStream.toByteArray());
}
/** Decodes a string, returning the resulting bytes interpreted as an ASCII String. */
private static String decodeToAscii(Decoder decoder, String encoded) throws Exception {
byte[] plain = decoder.decode(encoded);
return new String(plain, US_ASCII);
}
/**
* Checks round-trip encoding/decoding of {@code plain}.
*
* @param plain an ASCII String
* @return the Base64-encoded value of the ASCII codepoints from {@code plain}
*/
private static String encodeFromAscii(Encoder encoder, Decoder decoder, String plain)
throws Exception {
String encoded = encoder.encodeToString(plain.getBytes(US_ASCII));
String decoded = decodeToAscii(decoder, encoded);
assertEquals(plain, decoded);
return encoded;
}
/**
* Rewraps {@code s} by inserting {@lineSeparator} every {@code lineLength} characters,
* but not at the end.
*/
private static String wrapLines(String lineSeparator, String s, int lineLength) {
return String.join(lineSeparator, breakLines(s, lineLength));
}
/**
* Splits {@code s} into a list of substrings, each except possibly the last one
* exactly {@code lineLength} characters long.
*/
private static List<String> breakLines(String longString, int lineLength) {
List<String> lines = new ArrayList<>();
for (int pos = 0; pos < longString.length(); pos += lineLength) {
lines.add(longString.substring(pos, Math.min(longString.length(), pos + lineLength)));
}
return lines;
}
/** Assert that decoding the specific String throws IllegalArgumentException. */
private static void assertDecodeThrowsIAe(Decoder decoder, String invalidEncoded)
throws Exception {
try {
decoder.decode(invalidEncoded);
fail("should have failed to decode");
} catch (IllegalArgumentException e) {
}
}
/**
* Asserts that the given String decodes to the bytes, and that the bytes encode
* to the given String.
*/
private static void assertRoundTrip(Encoder encoder, Decoder decoder, String encoded,
byte[] bytes) {
assertEquals(encoded, encoder.encodeToString(bytes));
assertArrayEquals(bytes, decoder.decode(encoded));
}
/** Asserts that actual equals the first len bytes of expected. */
private static void assertArrayPrefixEquals(byte[] expected, int len, byte[] actual) {
assertArrayEquals(copyOfRange(expected, 0, len), actual);
}
/** Checks array contents. */
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("Expected " + hexString(expected) + ", got " + hexString(actual));
}
}
private static String hexString(byte[] bytes) {
StringBuilder sb = new StringBuilder("0x");
for (byte b : bytes) {
sb.append(Integer.toHexString(b & 0xff));
}
return sb.toString();
}
private static void assertThrowsNpe(Runnable runnable) {
try {
runnable.run();
fail("Should have thrown NullPointerException");
} catch (NullPointerException expected) {
}
}
}