blob: 043eba72263d9ec500f0d7dbcd31b131a434f236 [file] [log] [blame]
/**
* 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.
*/
package org.apache.cxf.common.util;
/**
* Base64Utility - this static class provides useful base64
* encoding utilities.
*/
// Java imports
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.logging.Logger;
import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.logging.LogUtils;
/**
* This class converts to/from base64. The alternative conversions include:
*
* encode:
* byte[] into String
* byte[] into char[]
* byte[] into OutStream
* byte[] into Writer
* decode:
* char[] into byte[]
* String into byte[]
* char[] into OutStream
* String into OutStream
*
*/
public final class Base64Utility {
private static final Logger LOG = LogUtils.getL7dLogger(Base64Utility.class);
// base 64 character set
//
private static final char[] BCS = {
'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 char[] BCS_URL_SAFE = Arrays.copyOf(BCS, BCS.length);
// base 64 padding
private static final char PAD = '=';
// size of base 64 decode table
private static final int BDTSIZE = 128;
// base 64 decode table
private static final byte[] BDT = new byte[128];
private static final int PAD_SIZE0 = 1;
private static final int PAD_SIZE4 = 2;
private static final int PAD_SIZE8 = 3;
// class static initializer for building decode table
static {
for (int i = 0; i < BDTSIZE; i++) {
BDT[i] = Byte.MAX_VALUE;
}
for (int i = 0; i < BCS.length; i++) {
BDT[BCS[i]] = (byte)i;
}
BCS_URL_SAFE[62] = '-';
BCS_URL_SAFE[63] = '_';
}
private Base64Utility() {
//utility class, never constructed
}
/**
* The <code>decode_chunk</code> routine decodes a chunk of data
* into its native encoding.
*
* base64 encodes each 3 octets of data into 4 characters from a
* limited 64 character set. The 3 octets are joined to form
* 24 bits which are then split into 4 x 6bit values. Each 6 bit
* value is then used as an index into the 64 character table of
* base64 chars. If the total data length is not a 3 octet multiple
* the '=' char is used as padding for the final 4 char group,
* either 1 octet + '==' or 2 octets + '='.
*
* @param id The input data to be processed
* @param o The offset from which to begin processing
* @param l The length (bound) at which processing is to end
* @return The decoded data
* @exception Base64Exception Thrown is processing fails due to
* formatting exceptions in the encoded data
*/
public static byte[] decodeChunk(char[] id,
int o,
int l)
throws Base64Exception {
if (id != null && id.length == 0 && l == 0) {
return new byte[0];
}
// Keep it simple - must be >= 4. Unpadded
// base64 data contain < 3 octets is invalid.
//
if ((l - o) < 4) {
return null;
}
char[] ib = new char[4];
int ibcount = 0;
// cryan. Calc the num of octets. Each 4 chars of base64 chars
// (representing 24 bits) encodes 3 octets.
//
int octetCount = 3 * (l / 4);
// Final 4 chars may contain 3 octets or padded to contain
// 1 or 2 octets.
//
if (id[l - 1] == PAD) {
// TT== means last 4 chars encode 8 bits (ie subtract 2)
// TTT= means last 4 chars encode 16 bits (ie subtract 1)
octetCount -= (id[l - 2] == PAD) ? 2 : 1;
}
byte[] ob = new byte[octetCount];
int obcount = 0;
for (int i = o; i < o + l && i < id.length; i++) {
if (id[i] == PAD
|| id[i] < BDT.length
&& BDT[id[i]] != Byte.MAX_VALUE) {
ib[ibcount++] = id[i];
// Decode each 4 char sequence.
//
if (ibcount == ib.length) {
ibcount = 0;
obcount += processEncodeme(ib, ob, obcount);
}
}
}
if (obcount != ob.length) {
byte []tmp = new byte[obcount];
System.arraycopy(ob, 0, tmp, 0, obcount);
ob = tmp;
}
return ob;
}
public static byte[] decode(String id) throws Base64Exception {
return decode(id, false);
}
public static byte[] decode(String id, boolean urlSafe) throws Base64Exception {
if (urlSafe) {
id = id.replace('-', '+').replace('_', '/');
switch (id.length() % 4) {
case 0:
break;
case 2:
id += "==";
break;
case 3:
id += "=";
break;
default:
throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG));
}
}
try {
char[] cd = id.toCharArray();
return decodeChunk(cd, 0, cd.length);
} catch (Exception e) {
LOG.warning("Invalid base64 encoded string : " + id);
throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
}
}
public static void decode(char[] id,
int o,
int l,
OutputStream ostream)
throws Base64Exception {
try {
ostream.write(decodeChunk(id, o, l));
} catch (Exception e) {
LOG.warning("Invalid base64 encoded string : " + new String(id));
throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
}
}
public static void decode(String id,
OutputStream ostream)
throws Base64Exception {
try {
char[] cd = id.toCharArray();
ostream.write(decodeChunk(cd, 0, cd.length));
} catch (IOException ioe) {
throw new Base64Exception(new Message("BASE64_DECODE_IOEXCEPTION", LOG), ioe);
} catch (Exception e) {
LOG.warning("Invalid base64 encoded string : " + id);
throw new Base64Exception(new Message("BASE64_RUNTIME_EXCEPTION", LOG), e);
}
}
// Returns base64 representation of specified byte array.
//
public static String encode(byte[] id) {
return encode(id, false);
}
public static String encode(byte[] id, boolean urlSafe) {
char[] cd = encodeChunk(id, 0, id.length);
return new String(cd, 0, cd.length);
}
// Returns base64 representation of specified byte array.
//
public static char[] encodeChunk(byte[] id,
int o,
int l) {
return encodeChunk(id, o, l, false);
}
public static char[] encodeChunk(byte[] id,
int o,
int l,
boolean urlSafe) {
if (id != null && id.length == 0 && l == 0) {
return new char[0];
} else if (l <= 0) {
return null;
}
char[] out;
// If not a multiple of 3 octets then a final padded 4 char
// slot is needed.
//
if (l % 3 == 0) {
out = new char[l / 3 * 4];
} else {
int finalLen = !urlSafe ? 4 : l % 3 == 1 ? 2 : 3;
out = new char[l / 3 * 4 + finalLen];
}
int rindex = o;
int windex = 0;
int rest = l;
final char[] base64Table = urlSafe ? BCS_URL_SAFE : BCS;
while (rest >= 3) {
int i = ((id[rindex] & 0xff) << 16)
+ ((id[rindex + 1] & 0xff) << 8)
+ (id[rindex + 2] & 0xff);
out[windex++] = base64Table[i >> 18];
out[windex++] = base64Table[(i >> 12) & 0x3f];
out[windex++] = base64Table[(i >> 6) & 0x3f];
out[windex++] = base64Table[i & 0x3f];
rindex += 3;
rest -= 3;
}
if (rest == 1) {
int i = id[rindex] & 0xff;
out[windex++] = base64Table[i >> 2];
out[windex++] = base64Table[(i << 4) & 0x3f];
if (!urlSafe) {
out[windex++] = PAD;
out[windex] = PAD;
}
} else if (rest == 2) {
int i = ((id[rindex] & 0xff) << 8) + (id[rindex + 1] & 0xff);
out[windex++] = base64Table[i >> 10];
out[windex++] = base64Table[(i >> 4) & 0x3f];
out[windex++] = base64Table[(i << 2) & 0x3f];
if (!urlSafe) {
out[windex] = PAD;
}
}
return out;
}
public static void encodeAndStream(byte[] id,
int o,
int l,
OutputStream os) throws IOException {
encodeAndStream(id, o, l, false, os);
}
public static void encodeAndStream(byte[] id,
int o,
int l,
boolean urlSafe,
OutputStream os) throws IOException {
if (l <= 0) {
return;
}
int rindex = o;
int rest = l;
final char[] base64Table = urlSafe ? BCS_URL_SAFE : BCS;
char[] chunk = new char[4];
while (rest >= 3) {
int i = ((id[rindex] & 0xff) << 16)
+ ((id[rindex + 1] & 0xff) << 8)
+ (id[rindex + 2] & 0xff);
chunk[0] = base64Table[i >> 18];
chunk[1] = base64Table[(i >> 12) & 0x3f];
chunk[2] = base64Table[(i >> 6) & 0x3f];
chunk[3] = base64Table[i & 0x3f];
writeCharArrayToStream(chunk, 4, os);
rindex += 3;
rest -= 3;
}
if (rest == 0) {
return;
}
if (rest == 1) {
int i = id[rindex] & 0xff;
chunk[0] = base64Table[i >> 2];
chunk[1] = base64Table[(i << 4) & 0x3f];
if (!urlSafe) {
chunk[2] = PAD;
chunk[3] = PAD;
}
} else if (rest == 2) {
int i = ((id[rindex] & 0xff) << 8) + (id[rindex + 1] & 0xff);
chunk[0] = base64Table[i >> 10];
chunk[1] = base64Table[(i >> 4) & 0x3f];
chunk[2] = base64Table[(i << 2) & 0x3f];
if (!urlSafe) {
chunk[3] = PAD;
}
}
int finalLenToWrite = !urlSafe ? 4 : rest == 1 ? 2 : 3;
writeCharArrayToStream(chunk, finalLenToWrite, os);
}
private static void writeCharArrayToStream(char[] chunk, int len, OutputStream os) throws IOException {
// may be we can just cast to byte when creating chunk[] earlier on
byte[] bytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chunk, 0, len)).array();
os.write(bytes);
}
//
// Outputs base64 representation of the specified byte array
// to a byte stream.
//
public static void encodeChunk(byte[] id,
int o,
int l,
OutputStream ostream) throws Base64Exception {
try {
ostream.write(new String(encodeChunk(id, o, l)).getBytes());
} catch (IOException e) {
throw new Base64Exception(new Message("BASE64_ENCODE_IOEXCEPTION", LOG), e);
}
}
// Outputs base64 representation of the specified byte
// array to a character stream.
//
public static void encode(byte[] id,
int o,
int l,
Writer writer) throws Base64Exception {
try {
writer.write(encodeChunk(id, o, l));
} catch (IOException e) {
throw new Base64Exception(new Message("BASE64_ENCODE_WRITER_IOEXCEPTION", LOG), e);
}
}
//---- Private static methods --------------------------------------
/**
* The <code>process</code> routine processes an atomic base64
* unit of encoding (encodeme) into its native encoding. This class is
* used by decode routines to do the grunt work of decoding
* base64 encoded information
*
* @param ib Input character buffer of encoded bytes
* @param ob Output byte buffer of decoded bytes
* @param p Pointer to the encodeme of interest
* @return The decoded encodeme
* @exception Base64Exception Thrown is processing fails due to
* formatting exceptions in the encoded data
*/
private static int processEncodeme(char[] ib,
byte[] ob,
int p)
throws Base64Exception {
int spad = PAD_SIZE8;
if (ib[3] == PAD) {
spad = PAD_SIZE4;
}
if (ib[2] == PAD) {
spad = PAD_SIZE0;
}
int b0 = BDT[ib[0]];
int b1 = BDT[ib[1]];
int b2 = BDT[ib[2]];
int b3 = BDT[ib[3]];
switch (spad) {
case PAD_SIZE0:
ob[p] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
return PAD_SIZE0;
case PAD_SIZE4:
ob[p++] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
ob[p] = (byte)(b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
return PAD_SIZE4;
case PAD_SIZE8:
ob[p++] = (byte)(b0 << 2 & 0xfc | b1 >> 4 & 0x3);
ob[p++] = (byte)(b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
ob[p] = (byte)(b2 << 6 & 0xc0 | b3 & 0x3f);
return PAD_SIZE8;
default:
// We should never get here
throw new IllegalStateException();
}
}
public static boolean isValidBase64(int ch) {
return ch == PAD || BDT[ch] != Byte.MAX_VALUE;
}
}