From 92929ee38d838ec47130c8e37c353dc6522b0114 Mon Sep 17 00:00:00 2001 From: Tobias Thierer Date: Thu, 1 Dec 2016 19:07:17 +0000 Subject: [PATCH] Drop deprecated libcore.io.Base64 in favor of java.util.Base64 Move the last last remaining user (DropBox) of l.i.Base64.encode() to j.u.Base64.getEncoder().encodeToString() which produces exactly the same result. The decoding behavior of the two implementations differ: - in case of error, l.i.Base64 returns null vs. j.u.Base64 throws - j.u.Base64 is more strict about forbidding extra in-stream or trailing characters - the two implementations differ in how many bytes they decode if the encoded data ends prematurely. To document the behavior change, the old libcore.io.Base64Test was kept with updated expectations and under a new name. Bug: 31292683 Test: libcore.java.util.LibcoreIoDerivedBase64Test Change-Id: I62d4731d38619629d72549430c57e07f8ea4aa1e --- luni/src/main/java/libcore/io/Base64.java | 266 ------------------ luni/src/main/java/libcore/io/DropBox.java | 4 +- .../util/LibcoreIoDerivedBase64Test.java} | 105 +++---- non_openjdk_java_files.mk | 1 - 4 files changed, 60 insertions(+), 316 deletions(-) delete mode 100644 luni/src/main/java/libcore/io/Base64.java rename luni/src/test/java/libcore/{io/Base64Test.java => java/util/LibcoreIoDerivedBase64Test.java} (78%) diff --git a/luni/src/main/java/libcore/io/Base64.java b/luni/src/main/java/libcore/io/Base64.java deleted file mode 100644 index f22bef48b..000000000 --- a/luni/src/main/java/libcore/io/Base64.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2015 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.io; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; - -/** - * 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(); - - private static byte[] initializeBase64Alphabet() { - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - .getBytes(StandardCharsets.US_ASCII); - } - - // Bit masks for the 4 output 6-bit values from 3 input bytes. - private static final int FIRST_OUTPUT_BYTE_MASK = 0x3f << 18; - private static final int SECOND_OUTPUT_BYTE_MASK = 0x3f << 12; - private static final int THIRD_OUTPUT_BYTE_MASK = 0x3f << 6; - private static final int FOURTH_OUTPUT_BYTE_MASK = 0x3f; - - private Base64() {} - - public static String encode(byte[] in) { - int len = in.length; - int outputLen = computeEncodingOutputLen(len); - byte[] output = new byte[outputLen]; - - int outputIndex = 0; - for (int i = 0; i < len; i += 3) { - // Only a "triplet" if there are there are at least three remaining bytes - // in the input... - // Mask with 0xff to avoid signed extension. - int byteTripletAsInt = in[i] & 0xff; - if (i + 1 < len) { - // Add second byte to the triplet. - byteTripletAsInt <<= 8; - byteTripletAsInt |= in[i + 1] & 0xff; - if (i + 2 < len) { - byteTripletAsInt <<= 8; - byteTripletAsInt |= in[i + 2] & 0xff; - } else { - // Insert 2 zero bits as to make output 18 bits long. - byteTripletAsInt <<= 2; - } - } else { - // Insert 4 zero bits as to make output 12 bits long. - byteTripletAsInt <<= 4; - } - - if (i + 2 < len) { - // The int may have up to 24 non-zero bits. - output[outputIndex++] = BASE_64_ALPHABET[ - (byteTripletAsInt & FIRST_OUTPUT_BYTE_MASK) >>> 18]; - } - if (i + 1 < len) { - // The int may have up to 18 non-zero bits. - output[outputIndex++] = BASE_64_ALPHABET[ - (byteTripletAsInt & SECOND_OUTPUT_BYTE_MASK) >>> 12]; - } - output[outputIndex++] = BASE_64_ALPHABET[ - (byteTripletAsInt & THIRD_OUTPUT_BYTE_MASK) >>> 6]; - output[outputIndex++] = BASE_64_ALPHABET[ - byteTripletAsInt & FOURTH_OUTPUT_BYTE_MASK]; - } - - int inLengthMod3 = len % 3; - // Add padding as per the spec. - if (inLengthMod3 > 0) { - output[outputIndex++] = '='; - if (inLengthMod3 == 1) { - output[outputIndex++] = '='; - } - } - - return new String(output, StandardCharsets.US_ASCII); - } - - private static int computeEncodingOutputLen(int inLength) { - int inLengthMod3 = inLength % 3; - int outputLen = (inLength / 3) * 4; - if (inLengthMod3 == 2) { - // Need 3 6-bit characters as to express the last 16 bits, plus 1 padding. - outputLen += 4; - } else if (inLengthMod3 == 1) { - // Need 2 6-bit characters as to express the last 8 bits, plus 2 padding. - outputLen += 4; - } - return outputLen; - } - - public static byte[] decode(byte[] in) { - return decode(in, in.length); - } - - /** Decodes the input from position 0 (inclusive) to len (exclusive). */ - public static byte[] decode(byte[] in, int len) { - final int inLength = Math.min(in.length, len); - // Overestimating 3 bytes per each 4 blocks of input (plus a possibly incomplete one). - ByteArrayOutputStream output = new ByteArrayOutputStream((inLength / 4) * 3 + 3); - // Position in the input. Use an array so we can pass it to {@code getNextByte}. - int[] pos = new int[1]; - - try { - while (pos[0] < inLength) { - int byteTripletAsInt = 0; - - // j is the index in a 4-tuple of 6-bit characters where are trying to read from the - // input. - for (int j = 0; j < 4; j++) { - byte c = getNextByte(in, pos, inLength); - if (c == END_OF_INPUT || c == PAD_AS_BYTE) { - // Padding or end of file... - switch (j) { - case 0: - case 1: - return (c == END_OF_INPUT) ? output.toByteArray() : null; - case 2: - // The input is over with two 6-bit characters: a single byte padded - // with 4 extra 0's. - - if (c == END_OF_INPUT) { - // Do not consider the block, since padding is not present. - return checkNoTrailingAndReturn(output, in, pos[0], inLength); - } - // We are at a pad character, consume and look for the second one. - pos[0]++; - c = getNextByte(in, pos, inLength); - if (c == END_OF_INPUT) { - // Do not consider the block, since padding is not present. - return checkNoTrailingAndReturn(output, in, pos[0], inLength); - } - if (c == PAD_AS_BYTE) { - byteTripletAsInt >>= 4; - output.write(byteTripletAsInt); - return checkNoTrailingAndReturn(output, in, pos[0], inLength); - } - // Something other than pad and non-alphabet characters, illegal. - return null; - - - case 3: - // The input is over with three 6-bit characters: two bytes padded - // with 2 extra 0's. - if (c == PAD_AS_BYTE) { - // Consider the block only if padding is present. - byteTripletAsInt >>= 2; - output.write(byteTripletAsInt >> 8); - output.write(byteTripletAsInt & 0xff); - } - return checkNoTrailingAndReturn(output, in, pos[0], inLength); - } - } else { - byteTripletAsInt <<= 6; - byteTripletAsInt += (c & 0xff); - pos[0]++; - } - } - // We have four 6-bit characters: output the corresponding 3 bytes - output.write(byteTripletAsInt >> 16); - output.write((byteTripletAsInt >> 8) & 0xff); - output.write(byteTripletAsInt & 0xff); - } - return checkNoTrailingAndReturn(output, in, pos[0], inLength); - } catch (InvalidBase64ByteException e) { - return null; - } - } - - /** - * On decoding, an illegal character always return null. - * - * Using this exception to avoid "if" checks every time. - */ - - private static class InvalidBase64ByteException extends Exception { } - - /** - * Obtain the numeric value corresponding to the next relevant byte in the input. - * - * Calculates the numeric value (6-bit, 0 <= x <= 63) of the next Base64 encoded byte in - * {@code in} at or after {@code pos[0]} and before {@code inLength}. Returns - * {@link #WHITESPACE_AS_BYTE}, {@link #PAD_AS_BYTE}, {@link #END_OF_INPUT} or the 6-bit value. - * {@code pos[0]} is updated as a side effect of this method. - */ - private static byte getNextByte(byte[] in, int[] pos, int inLength) - throws InvalidBase64ByteException { - // Ignore all whitespace. - while (pos[0] < inLength) { - byte c = base64AlphabetToNumericalValue(in[pos[0]]); - if (c != WHITESPACE_AS_BYTE) { - return c; - } - pos[0]++; - } - return END_OF_INPUT; - } - - /** - * Check that there are no invalid trailing characters (ie, other then whitespace and padding) - * - * Returns {@code output} as a byte array in case of success, {@code null} in case of invalid - * characters. - */ - private static byte[] checkNoTrailingAndReturn( - ByteArrayOutputStream output, byte[] in, int i, int inLength) - throws InvalidBase64ByteException{ - while (i < inLength) { - byte c = base64AlphabetToNumericalValue(in[i]); - if (c != WHITESPACE_AS_BYTE && c != PAD_AS_BYTE) { - return null; - } - i++; - } - return output.toByteArray(); - } - - private static final byte PAD_AS_BYTE = -1; - private static final byte WHITESPACE_AS_BYTE = -2; - private static final byte END_OF_INPUT = -3; - private static byte base64AlphabetToNumericalValue(byte c) throws InvalidBase64ByteException { - if ('A' <= c && c <= 'Z') { - return (byte) (c - 'A'); - } - if ('a' <= c && c <= 'z') { - return (byte) (c - 'a' + 26); - } - if ('0' <= c && c <= '9') { - return (byte) (c - '0' + 52); - } - if (c == '+') { - return (byte) 62; - } - if (c == '/') { - return (byte) 63; - } - if (c == '=') { - return PAD_AS_BYTE; - } - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { - return WHITESPACE_AS_BYTE; - } - throw new InvalidBase64ByteException(); - } -} diff --git a/luni/src/main/java/libcore/io/DropBox.java b/luni/src/main/java/libcore/io/DropBox.java index cf881060a..4180a2aee 100644 --- a/luni/src/main/java/libcore/io/DropBox.java +++ b/luni/src/main/java/libcore/io/DropBox.java @@ -16,6 +16,8 @@ package libcore.io; +import java.util.Base64; + public final class DropBox { /** @@ -54,7 +56,7 @@ public static interface Reporter { private static final class DefaultReporter implements Reporter { public void addData(String tag, byte[] data, int flags) { - System.out.println(tag + ": " + Base64.encode(data)); + System.out.println(tag + ": " + Base64.getEncoder().encodeToString(data)); } public void addText(String tag, String data) { diff --git a/luni/src/test/java/libcore/io/Base64Test.java b/luni/src/test/java/libcore/java/util/LibcoreIoDerivedBase64Test.java similarity index 78% rename from luni/src/test/java/libcore/io/Base64Test.java rename to luni/src/test/java/libcore/java/util/LibcoreIoDerivedBase64Test.java index 6cfe18616..2a888f76a 100644 --- a/luni/src/test/java/libcore/io/Base64Test.java +++ b/luni/src/test/java/libcore/java/util/LibcoreIoDerivedBase64Test.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package libcore.io; +package libcore.java.util; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -26,10 +26,16 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Base64; +import java.util.Base64.Decoder; import junit.framework.AssertionFailedError; import junit.framework.TestCase; -public final class Base64Test extends TestCase { +/** + * Additional tests for {@link java.util.Base64} derived from old tests for + * the removed class {@code libcore.io.Base64}. + */ +public final class LibcoreIoDerivedBase64Test extends TestCase { public void testEncodeDecode() throws Exception { assertEncodeDecode(""); @@ -59,7 +65,7 @@ private static void assertEncodeDecode(String expectedEncoded, int... toEncode) for (int i = 0; i < toEncode.length; i++) { inputBytes[i] = (byte) toEncode[i]; } - String encoded = Base64.encode(inputBytes); + String encoded = encode(inputBytes); assertEquals(expectedEncoded, encoded); // Check we can round-trip the encoded bytes to @@ -69,7 +75,7 @@ private static void assertEncodeDecode(String expectedEncoded, int... toEncode) } public void testDecode_empty() throws Exception { - byte[] decoded = Base64.decode(new byte[0]); + byte[] decoded = decode(new byte[0]); assertEquals(0, decoded.length); } @@ -78,26 +84,26 @@ public void testDecode_truncated() throws Exception { assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk")); // The following are missing the final bytes - assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcmx")); - assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvcm")); - assertEquals("hello, wo", decodeToString("aGVsbG8sIHdvc")); - assertEquals("hello, wo", decodeToString("aGVsbG8sIHdv")); + assertEquals("hello, worl", decodeToString("aGVsbG8sIHdvcmx")); + assertEquals("hello, wor", decodeToString("aGVsbG8sIHdvcm")); + assertEquals(null, decodeToString("aGVsbG8sIHdvc")); + assertEquals("hello, wo", decodeToString("aGVsbG8sIHdv")); } public void testDecode_extraChars() throws Exception { // Characters outside of alphabet before padding. - assertEquals("hello, world", decodeToString(" aGVsbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGV sbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk ")); + assertEquals(null, decodeToString(" aGVsbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGV sbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk ")); assertEquals(null, decodeToString("*aGVsbG8sIHdvcmxk")); assertEquals(null, decodeToString("aGV*sbG8sIHdvcmxk")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk*")); - assertEquals("hello, world", decodeToString("\r\naGVsbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGV\r\nsbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\r\n")); - assertEquals("hello, world", decodeToString("\naGVsbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGV\nsbG8sIHdvcmxk")); - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk\n")); + assertEquals(null, decodeToString("\r\naGVsbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGV\r\nsbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk\r\n")); + assertEquals(null, decodeToString("\naGVsbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGV\nsbG8sIHdvcmxk")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxk\n")); // padding 0 assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxk")); @@ -111,49 +117,49 @@ public void testDecode_extraChars() throws Exception { // padding 1 assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=")); // Missing padding - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkPyE")); + assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE")); // Characters outside alphabet before padding. - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE =")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE =")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE*=")); // Trailing characters, otherwise valid. - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= ")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE= ")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=*")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=X")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XY")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZ")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=XYZA")); - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\n")); - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE=\r\n")); - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE= ")); - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE==")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=\n")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE=\r\n")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE= ")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE==")); // Whitespace characters outside alphabet intermixed with (too much) padding. - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE ==")); - assertEquals("hello, world?!", decodeToString("aGVsbG8sIHdvcmxkPyE = = ")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE ==")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkPyE = = ")); // padding 2 assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==")); // Missing padding - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg")); + assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg")); // Partially missing padding - assertEquals("hello, world", decodeToString("aGVsbG8sIHdvcmxkLg=")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=")); // Characters outside alphabet before padding. - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg ==")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg ==")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg*==")); // Trailing characters, otherwise valid. - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== ")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg== ")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==*")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==X")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XY")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZ")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==XYZA")); - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\n")); - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg==\r\n")); - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg== ")); - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg===")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==\n")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg==\r\n")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg== ")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg===")); // Characters outside alphabet inside padding. - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg= =")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg= =")); assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=*=")); - assertEquals("hello, world.", decodeToString("aGVsbG8sIHdvcmxkLg=\r\n=")); + assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=\r\n=")); // Characters inside alphabet inside padding. assertEquals(null, decodeToString("aGVsbG8sIHdvcmxkLg=X=")); @@ -206,7 +212,7 @@ public void testDecode_urlAlphabet() throws Exception { */ private static String decodeToString(String in) throws Exception { byte[] bytes = asciiToBytes(in); - byte[] out = Base64.decode(bytes); + byte[] out = decode(bytes); if (out == null) { return null; } @@ -251,10 +257,23 @@ private static byte[] asciiToBytes(String string) { /** Decodes an ASCII string, returning an int array. */ private static int[] decodeToInts(String in) throws Exception { - byte[] bytes = Base64.decode(asciiToBytes(in)); + byte[] bytes = decode(asciiToBytes(in)); return bytesToInts(bytes); } + private static byte[] decode(byte[] encoded) { + Decoder decoder = Base64.getDecoder(); + try { + return decoder.decode(encoded); + } catch (IllegalArgumentException e) { + return null; + } + } + + private static String encode(byte[] data) { + return Base64.getEncoder().encodeToString(data); + } + /** * Convert a byte[] to an int[]. int is used because it is more convenient to use ints in * tests. @@ -270,16 +289,6 @@ private static int[] bytesToInts(byte[] bytes) { return ints; } - /** Assert that decoding 'in' throws ArrayIndexOutOfBoundsException. */ - private static void assertDecodeBad(String in) throws Exception { - try { - byte[] result = Base64.decode(asciiToBytes(in)); - fail("should have failed to decode. Actually received: " + - (result == null ? result : Arrays.toString(bytesToInts(result)))); - } catch (ArrayIndexOutOfBoundsException e) { - } - } - private static void assertArrayEquals(int[] expected, int[] actual) { assertSubArrayEquals(expected, expected.length, actual); } diff --git a/non_openjdk_java_files.mk b/non_openjdk_java_files.mk index c1a75654e..3d114757b 100644 --- a/non_openjdk_java_files.mk +++ b/non_openjdk_java_files.mk @@ -374,7 +374,6 @@ non_openjdk_java_files := \ luni/src/main/java/libcore/internal/StringPool.java \ luni/src/main/java/libcore/io/AsynchronousCloseMonitor.java \ luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java \ - luni/src/main/java/libcore/io/Base64.java \ luni/src/main/java/libcore/io/BlockGuardOs.java \ luni/src/main/java/libcore/io/BufferIterator.java \ luni/src/main/java/libcore/io/DropBox.java \