/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
/**
* Base64
provides Base64 encoding/decoding of strings and streams.
*/
public class Base64 {
// charset used for base64 encoded data (7-bit ASCII)
private static final String CHARSET = "US-ASCII";
// encoding table (the 64 valid base64 characters)
private static final char[] BASE64CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
// decoding table (used to lookup original 6-bit with base64 character
// as table index)
private static final byte[] DECODETABLE = new byte[128];
static {
// initialize decoding table
for (int i = 0; i < DECODETABLE.length; i++) {
DECODETABLE[i] = 0x7f;
}
// build decoding table
for (int i = 0; i < BASE64CHARS.length; i++) {
DECODETABLE[BASE64CHARS[i]] = (byte) i;
}
}
// pad character
private static final char BASE64PAD = '=';
/**
* empty private constructor
*/
private Base64() {
}
/**
* Calculates the size (i.e. number of bytes) of the base64 encoded output
* given the length (i.e. number of bytes) of the data to be encoded.
*
* @param dataLength length (i.e. number of bytes) of the data to be encoded
* @return size (i.e. number of bytes) of the base64 encoded output
*/
public static long calcEncodedLength(long dataLength) {
long encLen = dataLength * 4 / 3;
encLen += (encLen + 4) % 4;
return encLen;
}
/**
* Pessimistically guesses the size (i.e. number of bytes) of the decoded
* output given the length (i.e. number of bytes) of the base64 encoded
* data.
*
* @param encLength length (i.e. number of bytes) of the base64 encoded data
* @return size (i.e. number of bytes) of the decoded output
*/
public static long guessDecodedLength(long encLength) {
long decLen = encLength * 3 / 4;
return decLen + 3;
}
/**
* Outputs base64 representation of the specified stream data to a
* Writer
.
*
* @param in stream data to be encoded
* @param writer writer to output the encoded data
* @throws java.io.IOException if an i/o error occurs
*/
public static void encode(InputStream in, Writer writer)
throws IOException {
// encode stream data in chunks;
// chunksize must be a multiple of 3 in order
// to avoid padding within output
byte[] buffer = new byte[9 * 1024];
int read;
while ((read = in.read(buffer)) > 0) {
encode(buffer, 0, read, writer);
}
}
/**
* Outputs base64 representation of the specified stream data to an
* OutputStream
.
*
* @param in stream data to be encoded
* @param out stream where the encoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void encode(InputStream in, OutputStream out)
throws IOException {
Writer writer = new OutputStreamWriter(out, CHARSET);
encode(in, writer);
}
/**
* Outputs base64 representation of the specified data to a
* Writer
.
*
* @param data data to be encoded
* @param off offset within data at which to start encoding
* @param len length of data to encode
* @param writer writer to output the encoded data
* @throws java.io.IOException if an i/o error occurs
*/
public static void encode(byte[] data, int off, int len, Writer writer)
throws IOException {
if (len == 0) {
return;
}
if (len < 0 || off >= data.length
|| len + off > data.length) {
throw new IllegalArgumentException();
}
char[] enc = new char[4];
while (len >= 3) {
int i = ((data[off] & 0xff) << 16)
+ ((data[off + 1] & 0xff) << 8)
+ (data[off + 2] & 0xff);
enc[0] = BASE64CHARS[i >> 18];
enc[1] = BASE64CHARS[(i >> 12) & 0x3f];
enc[2] = BASE64CHARS[(i >> 6) & 0x3f];
enc[3] = BASE64CHARS[i & 0x3f];
writer.write(enc, 0, 4);
off += 3;
len -= 3;
}
// add padding if necessary
if (len == 1) {
int i = data[off] & 0xff;
enc[0] = BASE64CHARS[i >> 2];
enc[1] = BASE64CHARS[(i << 4) & 0x3f];
enc[2] = BASE64PAD;
enc[3] = BASE64PAD;
writer.write(enc, 0, 4);
} else if (len == 2) {
int i = ((data[off] & 0xff) << 8) + (data[off + 1] & 0xff);
enc[0] = BASE64CHARS[i >> 10];
enc[1] = BASE64CHARS[(i >> 4) & 0x3f];
enc[2] = BASE64CHARS[(i << 2) & 0x3f];
enc[3] = BASE64PAD;
writer.write(enc, 0, 4);
}
}
/**
* Decode base64 encoded data.
*
* @param reader reader for the base64 encoded data to be decoded
* @param out stream where the decoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void decode(Reader reader, OutputStream out)
throws IOException {
char[] chunk = new char[8192];
int read;
while ((read = reader.read(chunk)) > -1) {
decode(chunk, 0, read, out);
}
}
/**
* Decode base64 encoded data. The data read from the inputstream is
* assumed to be of charset "US-ASCII".
*
* @param in inputstream of the base64 encoded data to be decoded
* @param out stream where the decoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void decode(InputStream in, OutputStream out)
throws IOException {
decode(new InputStreamReader(in, CHARSET), out);
}
/**
* Decode base64 encoded data.
*
* @param data the base64 encoded data to be decoded
* @param out stream where the decoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void decode(String data, OutputStream out)
throws IOException {
char[] chars = data.toCharArray();
decode(chars, 0, chars.length, out);
}
/**
* Decode base64 encoded data.
*
* @param chars the base64 encoded data to be decoded
* @param out stream where the decoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void decode(char[] chars, OutputStream out)
throws IOException {
decode(chars, 0, chars.length, out);
}
/**
* Decode base64 encoded data.
*
* @param chars the base64 encoded data to be decoded
* @param off offset within data at which to start decoding
* @param len length of data to decode
* @param out stream where the decoded data should be written to
* @throws java.io.IOException if an i/o error occurs
*/
public static void decode(char[] chars, int off, int len, OutputStream out)
throws IOException {
if (len == 0) {
return;
}
if (len < 0 || off >= chars.length
|| len + off > chars.length) {
throw new IllegalArgumentException();
}
char[] chunk = new char[4];
byte[] dec = new byte[3];
int posChunk = 0;
// decode in chunks of 4 characters
for (int i = off; i < (off + len); i++) {
char c = chars[i];
if (c < DECODETABLE.length && DECODETABLE[c] != 0x7f
|| c == BASE64PAD) {
chunk[posChunk++] = c;
if (posChunk == chunk.length) {
int b0 = DECODETABLE[chunk[0]];
int b1 = DECODETABLE[chunk[1]];
int b2 = DECODETABLE[chunk[2]];
int b3 = DECODETABLE[chunk[3]];
if (chunk[3] == BASE64PAD && chunk[2] == BASE64PAD) {
dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
out.write(dec, 0, 1);
} else if (chunk[3] == BASE64PAD) {
dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
out.write(dec, 0, 2);
} else {
dec[0] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
dec[1] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
dec[2] = (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
out.write(dec, 0, 3);
}
posChunk = 0;
}
} else {
throw new IllegalArgumentException("specified data is not base64 encoded");
}
}
}
}