Add java.util.Base64 from OpenJDK8u60
The test was resurrected from abandoned CL http://r.android.com/93059
but was expanded and refactored.
libcore.os.Base64 should not be used for new code so it was marked
deprecated.
Test: vogar libcore/luni/src/test/java/libcore/java/util/Base64Test.java
Bug: 29935305
Change-Id: I37fa256a50f7ee8bae21cccf6a0db04dd0a117de
diff --git a/luni/src/main/java/libcore/io/Base64.java b/luni/src/main/java/libcore/io/Base64.java
index 236c166..f22bef4 100644
--- a/luni/src/main/java/libcore/io/Base64.java
+++ b/luni/src/main/java/libcore/io/Base64.java
@@ -22,7 +22,10 @@
/**
* Perform encoding and decoding of Base64 byte arrays as described in
* http://www.ietf.org/rfc/rfc2045.txt
+ *
+ * @deprecated use {@link java.util.Base64} instead
*/
+@Deprecated
public final class Base64 {
private static final byte[] BASE_64_ALPHABET = initializeBase64Alphabet();
diff --git a/luni/src/test/java/libcore/java/util/Base64Test.java b/luni/src/test/java/libcore/java/util/Base64Test.java
new file mode 100644
index 0000000..30aa177
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/Base64Test.java
@@ -0,0 +1,1138 @@
+/*
+ * 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) {
+ }
+ }
+
+}
diff --git a/ojluni/src/main/java/java/util/Base64.java b/ojluni/src/main/java/java/util/Base64.java
new file mode 100644
index 0000000..172acbe
--- /dev/null
+++ b/ojluni/src/main/java/java/util/Base64.java
@@ -0,0 +1,998 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.util;
+
+import java.io.FilterOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class consists exclusively of static methods for obtaining
+ * encoders and decoders for the Base64 encoding scheme. The
+ * implementation of this class supports the following types of Base64
+ * as specified in
+ * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
+ *
+ * <ul>
+ * <li><a name="basic"><b>Basic</b></a>
+ * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
+ * RFC 4648 and RFC 2045 for encoding and decoding operation.
+ * The encoder does not add any line feed (line separator)
+ * character. The decoder rejects data that contains characters
+ * outside the base64 alphabet.</p></li>
+ *
+ * <li><a name="url"><b>URL and Filename safe</b></a>
+ * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
+ * in Table 2 of RFC 4648 for encoding and decoding. The
+ * encoder does not add any line feed (line separator) character.
+ * The decoder rejects data that contains characters outside the
+ * base64 alphabet.</p></li>
+ *
+ * <li><a name="mime"><b>MIME</b></a>
+ * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
+ * RFC 2045 for encoding and decoding operation. The encoded output
+ * must be represented in lines of no more than 76 characters each
+ * and uses a carriage return {@code '\r'} followed immediately by
+ * a linefeed {@code '\n'} as the line separator. No line separator
+ * is added to the end of the encoded output. All line separators
+ * or other characters not found in the base64 alphabet table are
+ * ignored in decoding operation.</p></li>
+ * </ul>
+ *
+ * <p> Unless otherwise noted, passing a {@code null} argument to a
+ * method of this class will cause a {@link java.lang.NullPointerException
+ * NullPointerException} to be thrown.
+ *
+ * @author Xueming Shen
+ * @since 1.8
+ */
+
+public class Base64 {
+
+ private Base64() {}
+
+ /**
+ * Returns a {@link Encoder} that encodes using the
+ * <a href="#basic">Basic</a> type base64 encoding scheme.
+ *
+ * @return A Base64 encoder.
+ */
+ public static Encoder getEncoder() {
+ return Encoder.RFC4648;
+ }
+
+ /**
+ * Returns a {@link Encoder} that encodes using the
+ * <a href="#url">URL and Filename safe</a> type base64
+ * encoding scheme.
+ *
+ * @return A Base64 encoder.
+ */
+ public static Encoder getUrlEncoder() {
+ return Encoder.RFC4648_URLSAFE;
+ }
+
+ /**
+ * Returns a {@link Encoder} that encodes using the
+ * <a href="#mime">MIME</a> type base64 encoding scheme.
+ *
+ * @return A Base64 encoder.
+ */
+ public static Encoder getMimeEncoder() {
+ return Encoder.RFC2045;
+ }
+
+ /**
+ * Returns a {@link Encoder} that encodes using the
+ * <a href="#mime">MIME</a> type base64 encoding scheme
+ * with specified line length and line separators.
+ *
+ * @param lineLength
+ * the length of each output line (rounded down to nearest multiple
+ * of 4). If {@code lineLength <= 0} the output will not be separated
+ * in lines
+ * @param lineSeparator
+ * the line separator for each output line
+ *
+ * @return A Base64 encoder.
+ *
+ * @throws IllegalArgumentException if {@code lineSeparator} includes any
+ * character of "The Base64 Alphabet" as specified in Table 1 of
+ * RFC 2045.
+ */
+ public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
+ Objects.requireNonNull(lineSeparator);
+ int[] base64 = Decoder.fromBase64;
+ for (byte b : lineSeparator) {
+ if (base64[b & 0xff] != -1)
+ throw new IllegalArgumentException(
+ "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
+ }
+ if (lineLength <= 0) {
+ return Encoder.RFC4648;
+ }
+ return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true);
+ }
+
+ /**
+ * Returns a {@link Decoder} that decodes using the
+ * <a href="#basic">Basic</a> type base64 encoding scheme.
+ *
+ * @return A Base64 decoder.
+ */
+ public static Decoder getDecoder() {
+ return Decoder.RFC4648;
+ }
+
+ /**
+ * Returns a {@link Decoder} that decodes using the
+ * <a href="#url">URL and Filename safe</a> type base64
+ * encoding scheme.
+ *
+ * @return A Base64 decoder.
+ */
+ public static Decoder getUrlDecoder() {
+ return Decoder.RFC4648_URLSAFE;
+ }
+
+ /**
+ * Returns a {@link Decoder} that decodes using the
+ * <a href="#mime">MIME</a> type base64 decoding scheme.
+ *
+ * @return A Base64 decoder.
+ */
+ public static Decoder getMimeDecoder() {
+ return Decoder.RFC2045;
+ }
+
+ /**
+ * This class implements an encoder for encoding byte data using
+ * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
+ *
+ * <p> Instances of {@link Encoder} class are safe for use by
+ * multiple concurrent threads.
+ *
+ * <p> Unless otherwise noted, passing a {@code null} argument to
+ * a method of this class will cause a
+ * {@link java.lang.NullPointerException NullPointerException} to
+ * be thrown.
+ *
+ * @see Decoder
+ * @since 1.8
+ */
+ public static class Encoder {
+
+ private final byte[] newline;
+ private final int linemax;
+ private final boolean isURL;
+ private final boolean doPadding;
+
+ private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
+ this.isURL = isURL;
+ this.newline = newline;
+ this.linemax = linemax;
+ this.doPadding = doPadding;
+ }
+
+ /**
+ * This array is a lookup table that translates 6-bit positive integer
+ * index values into their "Base64 Alphabet" equivalents as specified
+ * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
+ */
+ private static final char[] toBase64 = {
+ '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', '+', '/'
+ };
+
+ /**
+ * It's the lookup table for "URL and Filename safe Base64" as specified
+ * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
+ * '_'. This table is used when BASE64_URL is specified.
+ */
+ private static final char[] toBase64URL = {
+ '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', '-', '_'
+ };
+
+ private static final int MIMELINEMAX = 76;
+ private static final byte[] CRLF = new byte[] {'\r', '\n'};
+
+ static final Encoder RFC4648 = new Encoder(false, null, -1, true);
+ static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
+ static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
+
+ private final int outLength(int srclen) {
+ int len = 0;
+ if (doPadding) {
+ len = 4 * ((srclen + 2) / 3);
+ } else {
+ int n = srclen % 3;
+ len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
+ }
+ if (linemax > 0) // line separators
+ len += (len - 1) / linemax * newline.length;
+ return len;
+ }
+
+ /**
+ * Encodes all bytes from the specified byte array into a newly-allocated
+ * byte array using the {@link Base64} encoding scheme. The returned byte
+ * array is of the length of the resulting bytes.
+ *
+ * @param src
+ * the byte array to encode
+ * @return A newly-allocated byte array containing the resulting
+ * encoded bytes.
+ */
+ public byte[] encode(byte[] src) {
+ int len = outLength(src.length); // dst array size
+ byte[] dst = new byte[len];
+ int ret = encode0(src, 0, src.length, dst);
+ if (ret != dst.length)
+ return Arrays.copyOf(dst, ret);
+ return dst;
+ }
+
+ /**
+ * Encodes all bytes from the specified byte array using the
+ * {@link Base64} encoding scheme, writing the resulting bytes to the
+ * given output byte array, starting at offset 0.
+ *
+ * <p> It is the responsibility of the invoker of this method to make
+ * sure the output byte array {@code dst} has enough space for encoding
+ * all bytes from the input byte array. No bytes will be written to the
+ * output byte array if the output byte array is not big enough.
+ *
+ * @param src
+ * the byte array to encode
+ * @param dst
+ * the output byte array
+ * @return The number of bytes written to the output byte array
+ *
+ * @throws IllegalArgumentException if {@code dst} does not have enough
+ * space for encoding all input bytes.
+ */
+ public int encode(byte[] src, byte[] dst) {
+ int len = outLength(src.length); // dst array size
+ if (dst.length < len)
+ throw new IllegalArgumentException(
+ "Output byte array is too small for encoding all input bytes");
+ return encode0(src, 0, src.length, dst);
+ }
+
+ /**
+ * Encodes the specified byte array into a String using the {@link Base64}
+ * encoding scheme.
+ *
+ * <p> This method first encodes all input bytes into a base64 encoded
+ * byte array and then constructs a new String by using the encoded byte
+ * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
+ * ISO-8859-1} charset.
+ *
+ * <p> In other words, an invocation of this method has exactly the same
+ * effect as invoking
+ * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
+ *
+ * @param src
+ * the byte array to encode
+ * @return A String containing the resulting Base64 encoded characters
+ */
+ @SuppressWarnings("deprecation")
+ public String encodeToString(byte[] src) {
+ byte[] encoded = encode(src);
+ return new String(encoded, 0, 0, encoded.length);
+ }
+
+ /**
+ * Encodes all remaining bytes from the specified byte buffer into
+ * a newly-allocated ByteBuffer using the {@link Base64} encoding
+ * scheme.
+ *
+ * Upon return, the source buffer's position will be updated to
+ * its limit; its limit will not have been changed. The returned
+ * output buffer's position will be zero and its limit will be the
+ * number of resulting encoded bytes.
+ *
+ * @param buffer
+ * the source ByteBuffer to encode
+ * @return A newly-allocated byte buffer containing the encoded bytes.
+ */
+ public ByteBuffer encode(ByteBuffer buffer) {
+ int len = outLength(buffer.remaining());
+ byte[] dst = new byte[len];
+ int ret = 0;
+ if (buffer.hasArray()) {
+ ret = encode0(buffer.array(),
+ buffer.arrayOffset() + buffer.position(),
+ buffer.arrayOffset() + buffer.limit(),
+ dst);
+ buffer.position(buffer.limit());
+ } else {
+ byte[] src = new byte[buffer.remaining()];
+ buffer.get(src);
+ ret = encode0(src, 0, src.length, dst);
+ }
+ if (ret != dst.length)
+ dst = Arrays.copyOf(dst, ret);
+ return ByteBuffer.wrap(dst);
+ }
+
+ /**
+ * Wraps an output stream for encoding byte data using the {@link Base64}
+ * encoding scheme.
+ *
+ * <p> It is recommended to promptly close the returned output stream after
+ * use, during which it will flush all possible leftover bytes to the underlying
+ * output stream. Closing the returned output stream will close the underlying
+ * output stream.
+ *
+ * @param os
+ * the output stream.
+ * @return the output stream for encoding the byte data into the
+ * specified Base64 encoded format
+ */
+ public OutputStream wrap(OutputStream os) {
+ Objects.requireNonNull(os);
+ return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
+ newline, linemax, doPadding);
+ }
+
+ /**
+ * Returns an encoder instance that encodes equivalently to this one,
+ * but without adding any padding character at the end of the encoded
+ * byte data.
+ *
+ * <p> The encoding scheme of this encoder instance is unaffected by
+ * this invocation. The returned encoder instance should be used for
+ * non-padding encoding operation.
+ *
+ * @return an equivalent encoder that encodes without adding any
+ * padding character at the end
+ */
+ public Encoder withoutPadding() {
+ if (!doPadding)
+ return this;
+ return new Encoder(isURL, newline, linemax, false);
+ }
+
+ private int encode0(byte[] src, int off, int end, byte[] dst) {
+ char[] base64 = isURL ? toBase64URL : toBase64;
+ int sp = off;
+ int slen = (end - off) / 3 * 3;
+ int sl = off + slen;
+ if (linemax > 0 && slen > linemax / 4 * 3)
+ slen = linemax / 4 * 3;
+ int dp = 0;
+ while (sp < sl) {
+ int sl0 = Math.min(sp + slen, sl);
+ for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
+ int bits = (src[sp0++] & 0xff) << 16 |
+ (src[sp0++] & 0xff) << 8 |
+ (src[sp0++] & 0xff);
+ dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
+ dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
+ dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f];
+ dst[dp0++] = (byte)base64[bits & 0x3f];
+ }
+ int dlen = (sl0 - sp) / 3 * 4;
+ dp += dlen;
+ sp = sl0;
+ if (dlen == linemax && sp < end) {
+ for (byte b : newline){
+ dst[dp++] = b;
+ }
+ }
+ }
+ if (sp < end) { // 1 or 2 leftover bytes
+ int b0 = src[sp++] & 0xff;
+ dst[dp++] = (byte)base64[b0 >> 2];
+ if (sp == end) {
+ dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
+ if (doPadding) {
+ dst[dp++] = '=';
+ dst[dp++] = '=';
+ }
+ } else {
+ int b1 = src[sp++] & 0xff;
+ dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
+ dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
+ if (doPadding) {
+ dst[dp++] = '=';
+ }
+ }
+ }
+ return dp;
+ }
+ }
+
+ /**
+ * This class implements a decoder for decoding byte data using the
+ * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
+ *
+ * <p> The Base64 padding character {@code '='} is accepted and
+ * interpreted as the end of the encoded byte data, but is not
+ * required. So if the final unit of the encoded byte data only has
+ * two or three Base64 characters (without the corresponding padding
+ * character(s) padded), they are decoded as if followed by padding
+ * character(s). If there is a padding character present in the
+ * final unit, the correct number of padding character(s) must be
+ * present, otherwise {@code IllegalArgumentException} (
+ * {@code IOException} when reading from a Base64 stream) is thrown
+ * during decoding.
+ *
+ * <p> Instances of {@link Decoder} class are safe for use by
+ * multiple concurrent threads.
+ *
+ * <p> Unless otherwise noted, passing a {@code null} argument to
+ * a method of this class will cause a
+ * {@link java.lang.NullPointerException NullPointerException} to
+ * be thrown.
+ *
+ * @see Encoder
+ * @since 1.8
+ */
+ public static class Decoder {
+
+ private final boolean isURL;
+ private final boolean isMIME;
+
+ private Decoder(boolean isURL, boolean isMIME) {
+ this.isURL = isURL;
+ this.isMIME = isMIME;
+ }
+
+ /**
+ * Lookup table for decoding unicode characters drawn from the
+ * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
+ * their 6-bit positive integer equivalents. Characters that
+ * are not in the Base64 alphabet but fall within the bounds of
+ * the array are encoded to -1.
+ *
+ */
+ private static final int[] fromBase64 = new int[256];
+ static {
+ Arrays.fill(fromBase64, -1);
+ for (int i = 0; i < Encoder.toBase64.length; i++)
+ fromBase64[Encoder.toBase64[i]] = i;
+ fromBase64['='] = -2;
+ }
+
+ /**
+ * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
+ * as specified in Table2 of the RFC 4648.
+ */
+ private static final int[] fromBase64URL = new int[256];
+
+ static {
+ Arrays.fill(fromBase64URL, -1);
+ for (int i = 0; i < Encoder.toBase64URL.length; i++)
+ fromBase64URL[Encoder.toBase64URL[i]] = i;
+ fromBase64URL['='] = -2;
+ }
+
+ static final Decoder RFC4648 = new Decoder(false, false);
+ static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
+ static final Decoder RFC2045 = new Decoder(false, true);
+
+ /**
+ * Decodes all bytes from the input byte array using the {@link Base64}
+ * encoding scheme, writing the results into a newly-allocated output
+ * byte array. The returned byte array is of the length of the resulting
+ * bytes.
+ *
+ * @param src
+ * the byte array to decode
+ *
+ * @return A newly-allocated byte array containing the decoded bytes.
+ *
+ * @throws IllegalArgumentException
+ * if {@code src} is not in valid Base64 scheme
+ */
+ public byte[] decode(byte[] src) {
+ byte[] dst = new byte[outLength(src, 0, src.length)];
+ int ret = decode0(src, 0, src.length, dst);
+ if (ret != dst.length) {
+ dst = Arrays.copyOf(dst, ret);
+ }
+ return dst;
+ }
+
+ /**
+ * Decodes a Base64 encoded String into a newly-allocated byte array
+ * using the {@link Base64} encoding scheme.
+ *
+ * <p> An invocation of this method has exactly the same effect as invoking
+ * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
+ *
+ * @param src
+ * the string to decode
+ *
+ * @return A newly-allocated byte array containing the decoded bytes.
+ *
+ * @throws IllegalArgumentException
+ * if {@code src} is not in valid Base64 scheme
+ */
+ public byte[] decode(String src) {
+ return decode(src.getBytes(StandardCharsets.ISO_8859_1));
+ }
+
+ /**
+ * Decodes all bytes from the input byte array using the {@link Base64}
+ * encoding scheme, writing the results into the given output byte array,
+ * starting at offset 0.
+ *
+ * <p> It is the responsibility of the invoker of this method to make
+ * sure the output byte array {@code dst} has enough space for decoding
+ * all bytes from the input byte array. No bytes will be be written to
+ * the output byte array if the output byte array is not big enough.
+ *
+ * <p> If the input byte array is not in valid Base64 encoding scheme
+ * then some bytes may have been written to the output byte array before
+ * IllegalargumentException is thrown.
+ *
+ * @param src
+ * the byte array to decode
+ * @param dst
+ * the output byte array
+ *
+ * @return The number of bytes written to the output byte array
+ *
+ * @throws IllegalArgumentException
+ * if {@code src} is not in valid Base64 scheme, or {@code dst}
+ * does not have enough space for decoding all input bytes.
+ */
+ public int decode(byte[] src, byte[] dst) {
+ int len = outLength(src, 0, src.length);
+ if (dst.length < len)
+ throw new IllegalArgumentException(
+ "Output byte array is too small for decoding all input bytes");
+ return decode0(src, 0, src.length, dst);
+ }
+
+ /**
+ * Decodes all bytes from the input byte buffer using the {@link Base64}
+ * encoding scheme, writing the results into a newly-allocated ByteBuffer.
+ *
+ * <p> Upon return, the source buffer's position will be updated to
+ * its limit; its limit will not have been changed. The returned
+ * output buffer's position will be zero and its limit will be the
+ * number of resulting decoded bytes
+ *
+ * <p> {@code IllegalArgumentException} is thrown if the input buffer
+ * is not in valid Base64 encoding scheme. The position of the input
+ * buffer will not be advanced in this case.
+ *
+ * @param buffer
+ * the ByteBuffer to decode
+ *
+ * @return A newly-allocated byte buffer containing the decoded bytes
+ *
+ * @throws IllegalArgumentException
+ * if {@code src} is not in valid Base64 scheme.
+ */
+ public ByteBuffer decode(ByteBuffer buffer) {
+ int pos0 = buffer.position();
+ try {
+ byte[] src;
+ int sp, sl;
+ if (buffer.hasArray()) {
+ src = buffer.array();
+ sp = buffer.arrayOffset() + buffer.position();
+ sl = buffer.arrayOffset() + buffer.limit();
+ buffer.position(buffer.limit());
+ } else {
+ src = new byte[buffer.remaining()];
+ buffer.get(src);
+ sp = 0;
+ sl = src.length;
+ }
+ byte[] dst = new byte[outLength(src, sp, sl)];
+ return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
+ } catch (IllegalArgumentException iae) {
+ buffer.position(pos0);
+ throw iae;
+ }
+ }
+
+ /**
+ * Returns an input stream for decoding {@link Base64} encoded byte stream.
+ *
+ * <p> The {@code read} methods of the returned {@code InputStream} will
+ * throw {@code IOException} when reading bytes that cannot be decoded.
+ *
+ * <p> Closing the returned input stream will close the underlying
+ * input stream.
+ *
+ * @param is
+ * the input stream
+ *
+ * @return the input stream for decoding the specified Base64 encoded
+ * byte stream
+ */
+ public InputStream wrap(InputStream is) {
+ Objects.requireNonNull(is);
+ return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
+ }
+
+ private int outLength(byte[] src, int sp, int sl) {
+ int[] base64 = isURL ? fromBase64URL : fromBase64;
+ int paddings = 0;
+ int len = sl - sp;
+ if (len == 0)
+ return 0;
+ if (len < 2) {
+ if (isMIME && base64[0] == -1)
+ return 0;
+ throw new IllegalArgumentException(
+ "Input byte[] should at least have 2 bytes for base64 bytes");
+ }
+ if (isMIME) {
+ // scan all bytes to fill out all non-alphabet. a performance
+ // trade-off of pre-scan or Arrays.copyOf
+ int n = 0;
+ while (sp < sl) {
+ int b = src[sp++] & 0xff;
+ if (b == '=') {
+ len -= (sl - sp + 1);
+ break;
+ }
+ if ((b = base64[b]) == -1)
+ n++;
+ }
+ len -= n;
+ } else {
+ if (src[sl - 1] == '=') {
+ paddings++;
+ if (src[sl - 2] == '=')
+ paddings++;
+ }
+ }
+ if (paddings == 0 && (len & 0x3) != 0)
+ paddings = 4 - (len & 0x3);
+ return 3 * ((len + 3) / 4) - paddings;
+ }
+
+ private int decode0(byte[] src, int sp, int sl, byte[] dst) {
+ int[] base64 = isURL ? fromBase64URL : fromBase64;
+ int dp = 0;
+ int bits = 0;
+ int shiftto = 18; // pos of first byte of 4-byte atom
+ while (sp < sl) {
+ int b = src[sp++] & 0xff;
+ if ((b = base64[b]) < 0) {
+ if (b == -2) { // padding byte '='
+ // = shiftto==18 unnecessary padding
+ // x= shiftto==12 a dangling single x
+ // x to be handled together with non-padding case
+ // xx= shiftto==6&&sp==sl missing last =
+ // xx=y shiftto==6 last is not =
+ if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
+ shiftto == 18) {
+ throw new IllegalArgumentException(
+ "Input byte array has wrong 4-byte ending unit");
+ }
+ break;
+ }
+ if (isMIME) // skip if for rfc2045
+ continue;
+ else
+ throw new IllegalArgumentException(
+ "Illegal base64 character " +
+ Integer.toString(src[sp - 1], 16));
+ }
+ bits |= (b << shiftto);
+ shiftto -= 6;
+ if (shiftto < 0) {
+ dst[dp++] = (byte)(bits >> 16);
+ dst[dp++] = (byte)(bits >> 8);
+ dst[dp++] = (byte)(bits);
+ shiftto = 18;
+ bits = 0;
+ }
+ }
+ // reached end of byte array or hit padding '=' characters.
+ if (shiftto == 6) {
+ dst[dp++] = (byte)(bits >> 16);
+ } else if (shiftto == 0) {
+ dst[dp++] = (byte)(bits >> 16);
+ dst[dp++] = (byte)(bits >> 8);
+ } else if (shiftto == 12) {
+ // dangling single "x", incorrectly encoded.
+ throw new IllegalArgumentException(
+ "Last unit does not have enough valid bits");
+ }
+ // anything left is invalid, if is not MIME.
+ // if MIME, ignore all non-base64 character
+ while (sp < sl) {
+ if (isMIME && base64[src[sp++]] < 0)
+ continue;
+ throw new IllegalArgumentException(
+ "Input byte array has incorrect ending byte at " + sp);
+ }
+ return dp;
+ }
+ }
+
+ /*
+ * An output stream for encoding bytes into the Base64.
+ */
+ private static class EncOutputStream extends FilterOutputStream {
+
+ private int leftover = 0;
+ private int b0, b1, b2;
+ private boolean closed = false;
+
+ private final char[] base64; // byte->base64 mapping
+ private final byte[] newline; // line separator, if needed
+ private final int linemax;
+ private final boolean doPadding;// whether or not to pad
+ private int linepos = 0;
+
+ EncOutputStream(OutputStream os, char[] base64,
+ byte[] newline, int linemax, boolean doPadding) {
+ super(os);
+ this.base64 = base64;
+ this.newline = newline;
+ this.linemax = linemax;
+ this.doPadding = doPadding;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ byte[] buf = new byte[1];
+ buf[0] = (byte)(b & 0xff);
+ write(buf, 0, 1);
+ }
+
+ private void checkNewline() throws IOException {
+ if (linepos == linemax) {
+ out.write(newline);
+ linepos = 0;
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (closed)
+ throw new IOException("Stream is closed");
+ if (off < 0 || len < 0 || off + len > b.length)
+ throw new ArrayIndexOutOfBoundsException();
+ if (len == 0)
+ return;
+ if (leftover != 0) {
+ if (leftover == 1) {
+ b1 = b[off++] & 0xff;
+ len--;
+ if (len == 0) {
+ leftover++;
+ return;
+ }
+ }
+ b2 = b[off++] & 0xff;
+ len--;
+ checkNewline();
+ out.write(base64[b0 >> 2]);
+ out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
+ out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
+ out.write(base64[b2 & 0x3f]);
+ linepos += 4;
+ }
+ int nBits24 = len / 3;
+ leftover = len - (nBits24 * 3);
+ while (nBits24-- > 0) {
+ checkNewline();
+ int bits = (b[off++] & 0xff) << 16 |
+ (b[off++] & 0xff) << 8 |
+ (b[off++] & 0xff);
+ out.write(base64[(bits >>> 18) & 0x3f]);
+ out.write(base64[(bits >>> 12) & 0x3f]);
+ out.write(base64[(bits >>> 6) & 0x3f]);
+ out.write(base64[bits & 0x3f]);
+ linepos += 4;
+ }
+ if (leftover == 1) {
+ b0 = b[off++] & 0xff;
+ } else if (leftover == 2) {
+ b0 = b[off++] & 0xff;
+ b1 = b[off++] & 0xff;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ closed = true;
+ if (leftover == 1) {
+ checkNewline();
+ out.write(base64[b0 >> 2]);
+ out.write(base64[(b0 << 4) & 0x3f]);
+ if (doPadding) {
+ out.write('=');
+ out.write('=');
+ }
+ } else if (leftover == 2) {
+ checkNewline();
+ out.write(base64[b0 >> 2]);
+ out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
+ out.write(base64[(b1 << 2) & 0x3f]);
+ if (doPadding) {
+ out.write('=');
+ }
+ }
+ leftover = 0;
+ out.close();
+ }
+ }
+ }
+
+ /*
+ * An input stream for decoding Base64 bytes
+ */
+ private static class DecInputStream extends InputStream {
+
+ private final InputStream is;
+ private final boolean isMIME;
+ private final int[] base64; // base64 -> byte mapping
+ private int bits = 0; // 24-bit buffer for decoding
+ private int nextin = 18; // next available "off" in "bits" for input;
+ // -> 18, 12, 6, 0
+ private int nextout = -8; // next available "off" in "bits" for output;
+ // -> 8, 0, -8 (no byte for output)
+ private boolean eof = false;
+ private boolean closed = false;
+
+ DecInputStream(InputStream is, int[] base64, boolean isMIME) {
+ this.is = is;
+ this.base64 = base64;
+ this.isMIME = isMIME;
+ }
+
+ private byte[] sbBuf = new byte[1];
+
+ @Override
+ public int read() throws IOException {
+ return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (closed)
+ throw new IOException("Stream is closed");
+ if (eof && nextout < 0) // eof and no leftover
+ return -1;
+ if (off < 0 || len < 0 || len > b.length - off)
+ throw new IndexOutOfBoundsException();
+ int oldOff = off;
+ if (nextout >= 0) { // leftover output byte(s) in bits buf
+ do {
+ if (len == 0)
+ return off - oldOff;
+ b[off++] = (byte)(bits >> nextout);
+ len--;
+ nextout -= 8;
+ } while (nextout >= 0);
+ bits = 0;
+ }
+ while (len > 0) {
+ int v = is.read();
+ if (v == -1) {
+ eof = true;
+ if (nextin != 18) {
+ if (nextin == 12)
+ throw new IOException("Base64 stream has one un-decoded dangling byte.");
+ // treat ending xx/xxx without padding character legal.
+ // same logic as v == '=' below
+ b[off++] = (byte)(bits >> (16));
+ len--;
+ if (nextin == 0) { // only one padding byte
+ if (len == 0) { // no enough output space
+ bits >>= 8; // shift to lowest byte
+ nextout = 0;
+ } else {
+ b[off++] = (byte) (bits >> 8);
+ }
+ }
+ }
+ if (off == oldOff)
+ return -1;
+ else
+ return off - oldOff;
+ }
+ if (v == '=') { // padding byte(s)
+ // = shiftto==18 unnecessary padding
+ // x= shiftto==12 dangling x, invalid unit
+ // xx= shiftto==6 && missing last '='
+ // xx=y or last is not '='
+ if (nextin == 18 || nextin == 12 ||
+ nextin == 6 && is.read() != '=') {
+ throw new IOException("Illegal base64 ending sequence:" + nextin);
+ }
+ b[off++] = (byte)(bits >> (16));
+ len--;
+ if (nextin == 0) { // only one padding byte
+ if (len == 0) { // no enough output space
+ bits >>= 8; // shift to lowest byte
+ nextout = 0;
+ } else {
+ b[off++] = (byte) (bits >> 8);
+ }
+ }
+ eof = true;
+ break;
+ }
+ if ((v = base64[v]) == -1) {
+ if (isMIME) // skip if for rfc2045
+ continue;
+ else
+ throw new IOException("Illegal base64 character " +
+ Integer.toString(v, 16));
+ }
+ bits |= (v << nextin);
+ if (nextin == 0) {
+ nextin = 18; // clear for next
+ nextout = 16;
+ while (nextout >= 0) {
+ b[off++] = (byte)(bits >> nextout);
+ len--;
+ nextout -= 8;
+ if (len == 0 && nextout >= 0) { // don't clean "bits"
+ return off - oldOff;
+ }
+ }
+ bits = 0;
+ } else {
+ nextin -= 6;
+ }
+ }
+ return off - oldOff;
+ }
+
+ @Override
+ public int available() throws IOException {
+ if (closed)
+ throw new IOException("Stream is closed");
+ return is.available(); // TBD:
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (!closed) {
+ closed = true;
+ is.close();
+ }
+ }
+ }
+}
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 1238d5a..6ace483 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -757,6 +757,7 @@
ojluni/src/main/java/java/util/ArrayPrefixHelpers.java \
ojluni/src/main/java/java/util/Arrays.java \
ojluni/src/main/java/java/util/ArraysParallelSortHelpers.java \
+ ojluni/src/main/java/java/util/Base64.java \
ojluni/src/main/java/java/util/BitSet.java \
ojluni/src/main/java/java/util/Calendar.java \
ojluni/src/main/java/java/util/Collection.java \