
com.pippsford.encoding.GenericBase64 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common-utils Show documentation
Show all versions of common-utils Show documentation
Code that seems to me to have utility across multiple projects
The newest version!
package com.pippsford.encoding;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* A generic Base 64 converter that can handle the normal variations between different Base 64 schemes.
*
* @author Simon Greatrix
*/
// Suppressing "cognitive complexity" warnings as data validation requires a lot of if-then constructs
// which leads to a high cognitive complexity. Sometimes there are just a lot of rules to check.
@SuppressWarnings("squid:S3776")
@ThreadSafe
public class GenericBase64 implements Converter {
private static final String ERROR_INVALID_CHAR = "Input text has invalid character 0x%x at position %d : %s";
/**
* Characters for each of the values 0 to 63.
*/
final char[] chars = new char[64];
/**
* Must padding be applied on encoding?.
*/
final boolean mustPad;
/**
* The padding character.
*/
final char pad;
/**
* Contributions of each character to each of the three bytes in a block. Each byte has two contributing characters.
*/
final byte[][] values = new byte[6][128];
/**
* Create a new generic Base 64 converter.
*
* @param c62 the character to use for 62
* @param c63 the character to use for 63
* @param pad the padding character, if any
* @param mustPad whether encoding must be padded
*/
public GenericBase64(char c62, char c63, char pad, boolean mustPad) {
this.pad = pad;
this.mustPad = mustPad;
int j = 0;
for (char i = 'A'; i <= 'Z'; i++) {
chars[j] = i;
j++;
}
for (char i = 'a'; i <= 'z'; i++) {
chars[j] = i;
j++;
}
for (char i = '0'; i <= '9'; i++) {
chars[j] = i;
j++;
}
chars[62] = c62;
chars[63] = c63;
for (int i = 0; i < 6; i++) {
for (j = 0; j < 128; j++) {
values[i][j] = -1;
}
}
for (int i = 0; i < 64; i++) {
j = chars[i];
values[5][j] = (byte) i;
values[4][j] = (byte) ((i & 0x03) << 6);
values[3][j] = (byte) ((i & 0x3c) >> 2);
values[2][j] = (byte) ((i & 0x0f) << 4);
values[1][j] = (byte) ((i & 0x30) >> 4);
values[0][j] = (byte) ((i & 0x3f) << 2);
}
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public char[] clean(char[] text) {
if (text == null) {
return null;
}
char[] buf = new char[text.length];
int pos = 0;
for (char ch : text) {
if ((ch < 128) && values[5][ch] != -1) {
buf = TextToByte.append(buf, pos++, ch);
}
}
int r = pos % 4;
// Try to repair trailing bits by adding a zero.
char lastChar;
switch (r) {
case 1:
// Incomplete first byte, always append zero
buf = TextToByte.append(buf, pos++, chars[0]);
r = 2;
break;
case 2:
// Incomplete second byte if bits not zero
lastChar = lastChar(text);
if (values[2][lastChar] != 0) {
buf = TextToByte.append(buf, pos++, chars[0]);
r = 3;
}
break;
case 3:
// Incomplete third byte if bits not zero
lastChar = lastChar(text);
if (values[4][lastChar] != 0) {
buf = TextToByte.append(buf, pos++, chars[0]);
r = 0;
}
break;
default:
break;
}
if (mustPad) {
if (r >= 2) {
buf = TextToByte.append(buf, pos++, pad);
}
if (r == 2) {
buf = TextToByte.append(buf, pos++, pad);
}
}
return TextToByte.trim(buf, pos);
}
/**
* Decode the provided textual representation back into binary data.
*
* @param text textual representation
*
* @return binary data
*/
@Nullable
@Override
public byte[] decode(char[] text) {
if (text == null) {
return null;
}
int end = TextToByte.removeWhitespaceInPlace(text);
while ((end > 0) && (text[end - 1] == pad)) {
end--;
}
if (end == 0) {
return new byte[0];
}
// validate length after padding removed
int rem = end & 0x3;
if (rem == 1) {
throw new IllegalArgumentException(
"Input text has invalid length of " + end + ": " + new String(text));
}
// every 4 characters produces 3 bytes
int byteLen = 3 * ((end - rem) / 4);
if (rem == 2) {
byteLen += 1;
} else if (rem == 3) {
byteLen += 2;
}
byte[] data = new byte[byteLen];
// extract data from text
int ti = 0;
int r = 0;
char c0;
char c1 = '\0';
for (int di = 0; di < byteLen; di++) {
if (r == 0) {
c0 = text[ti];
if ((c0 > 127) || values[5][c0] == -1) {
throw new IllegalArgumentException(String.format(ERROR_INVALID_CHAR, (int) c0, ti, new String(text)));
}
ti++;
c1 = text[ti];
if ((c1 > 127) || values[5][c1] == -1) {
throw new IllegalArgumentException(String.format(ERROR_INVALID_CHAR, (int) c1, ti, new String(text)));
}
ti++;
} else {
c0 = c1;
c1 = text[ti];
if ((c1 > 127) || values[5][c1] == -1) {
throw new IllegalArgumentException(String.format(ERROR_INVALID_CHAR, (int) c1, ti, new String(text)));
}
ti++;
}
data[di] = (byte) (values[r * 2][c0] + values[r * 2 + 1][c1]);
r = (r + 1) % 3;
}
return data;
}
/**
* Encode the provided binary data in a textual form.
*
* @param bytes binary data
*
* @return textual representation
*/
@Nullable
@Override
public char[] encodeChars(byte[] bytes) {
if (bytes == null) {
return null;
}
// every three bytes requires 4 characters of output
int fullBlocks = bytes.length / 3;
int extraBytes = bytes.length - 3 * fullBlocks;
int textLen = 4 * fullBlocks;
if (extraBytes != 0) {
if (mustPad) {
textLen += 4;
} else {
textLen += 1 + extraBytes;
}
}
char[] output = new char[textLen];
int b;
for (int i = 0; i < fullBlocks; i++) {
int j = i * 3;
int k = i * 4;
b = ((0xff & bytes[j]) << 16) | ((0xff & bytes[j + 1]) << 8) | (0xff & bytes[j + 2]);
output[k] = chars[(b >> 18) & 0x3f];
output[k + 1] = chars[(b >> 12) & 0x3f];
output[k + 2] = chars[(b >> 6) & 0x3f];
output[k + 3] = chars[b & 0x3f];
}
int k = fullBlocks * 4;
int j = fullBlocks * 3;
switch (extraBytes) {
case 1:
b = 0xff & bytes[j];
output[k] = chars[(b >> 2) & 0x3f];
output[k + 1] = chars[(b & 0x3) << 4];
if (mustPad) {
output[k + 2] = pad;
output[k + 3] = pad;
}
break;
case 2:
b = ((0xff & bytes[j]) << 8) | (0xff & bytes[j + 1]);
output[k] = chars[(b >> 10) & 0x3f];
output[k + 1] = chars[(b >> 4) & 0x3f];
output[k + 2] = chars[(b << 2) & 0x3f];
if (mustPad) {
output[k + 3] = pad;
}
break;
default:
break;
}
return output;
}
private char lastChar(char[] text) {
for (int i = text.length - 1; i >= 0; i--) {
char ch = text[i];
if ((ch < 128) && values[5][ch] != -1) {
return ch;
}
}
// never happens
return (char) 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy