/* * 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 */ 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(); } }