libcore.io.Base64 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-all Show documentation
Show all versions of android-all Show documentation
A library jar that provides APIs for Applications written for the Google Android Platform.
/*
* 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();
}
}