Skip to content

Instantly share code, notes, and snippets.

@rstiller
Created July 30, 2012 05:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rstiller/3204952 to your computer and use it in GitHub Desktop.
Save rstiller/3204952 to your computer and use it in GitHub Desktop.
B64
package test;
import java.io.IOException;
import java.io.OutputStream;
public class Base64OutputStream extends OutputStream {
public static final int DEFAULT_BUFFER_SIZE = 65536;
public static final String DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
public static final String BASE64_URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
public static final char DEFAULT_PADDING = '=';
private class CombinedByteArrayWrapper {
protected byte[] data;
protected int length;
protected int offset;
protected int difference;
public int getLength() {
return length + difference;
}
public int getOffset() {
return offset;
}
public void setLength(final int newLength) {
length = newLength;
}
public void setOffset(final int newOffset) {
offset = newOffset;
}
public void setData(final byte[] newData) {
difference = overflow[0] != -1 ? 1 : 0;
difference = overflow[1] != -1 ? 2 : difference;
data = newData;
}
public byte get(final int index) {
return difference > 0 && index < difference ? overflow[index - offset] : data[index - difference];
}
}
protected char[] alphabet = DEFAULT_ALPHABET.toCharArray();
protected char padding = DEFAULT_PADDING;
protected byte[] buffer;
protected byte[] overflow = new byte[2];
protected int currentBufferIndex;
protected CombinedByteArrayWrapper wrapper = new CombinedByteArrayWrapper();
OutputStream stream;
byte[] singleByte = new byte[1];
public Base64OutputStream() {
buffer = new byte[DEFAULT_BUFFER_SIZE];
}
public Base64OutputStream(final String alphabet, final char padding, final int bufferSize) {
if (bufferSize % 4 != 0) {
throw new IllegalArgumentException("bufferSize needs to be restless dividable through 4");
}
this.alphabet = alphabet.toCharArray();
this.padding = padding;
buffer = new byte[bufferSize];
}
public void reset(final OutputStream stream) {
this.stream = stream;
currentBufferIndex = 0;
overflow[0] = overflow[1] = -1;
}
@Override
public void write(final int b) throws IOException {
singleByte[0] = (byte) b;
write(singleByte, 0, singleByte.length);
}
@Override
public void write(final byte[] data, final int offset, final int length) throws IOException {
wrapper.setData(data);
wrapper.setLength(length);
wrapper.setOffset(offset);
decode();
}
protected void decode() throws IOException {
int offset = wrapper.getOffset(), pos = offset, index, end, rest, length = wrapper.getLength();
end = length - 3;
for (int i = 0; i <= end; i += 3, pos += 3, currentBufferIndex += 4) {
index = (wrapper.get(pos) & 0xfc) >> 2;
buffer[currentBufferIndex] = (byte) alphabet[index];
index = ((wrapper.get(pos) & 0x03) << 4) | ((wrapper.get(pos + 1) & 0xf0) >> 4);
buffer[currentBufferIndex + 1] = (byte) alphabet[index];
index = ((wrapper.get(pos + 1) & 0x0f) << 2) | ((wrapper.get(pos + 2) & 0xc0) >> 6);
buffer[currentBufferIndex + 2] = (byte) alphabet[index];
index = wrapper.get(pos + 2) & 0x3f;
buffer[currentBufferIndex + 3] = (byte) alphabet[index];
if (currentBufferIndex + 4 == buffer.length) {
stream.write(buffer, 0, buffer.length);
currentBufferIndex = -4;
}
}
overflow[0] = overflow[1] = -1;
if (length % 3 != 0) {
rest = length - (pos - offset);
if (rest == 2) {
overflow[0] = wrapper.get(pos);
overflow[1] = wrapper.get(pos + 1);
} else if (rest == 1) {
overflow[0] = wrapper.get(pos);
overflow[1] = -1;
}
}
}
@Override
public void flush() throws IOException {
int index;
if (overflow[1] != -1) {
index = (overflow[0] & 0xfc) >> 2;
buffer[currentBufferIndex + 0] = (byte) alphabet[index];
index = ((overflow[0] & 0x03) << 4) | ((overflow[1] & 0xf0) >> 4);
buffer[currentBufferIndex + 1] = (byte) alphabet[index];
index = (overflow[1] & 0x0f) << 2;
buffer[currentBufferIndex + 2] = (byte) alphabet[index];
buffer[currentBufferIndex + 3] = (byte) padding;
currentBufferIndex += 4;
} else if (overflow[0] != -1) {
index = (overflow[0] & 0xfc) >> 2;
buffer[currentBufferIndex + 0] = (byte) alphabet[index];
index = (overflow[0] & 0x03) << 4;
buffer[currentBufferIndex + 1] = (byte) alphabet[index];
buffer[currentBufferIndex + 2] = (byte) padding;
buffer[currentBufferIndex + 3] = (byte) padding;
currentBufferIndex += 4;
}
if (currentBufferIndex != -4 && currentBufferIndex % 4 == 0) {
stream.write(buffer, 0, currentBufferIndex);
currentBufferIndex = 0;
}
overflow[0] = overflow[1] = -1;
super.flush();
}
}
package test;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import org.junit.Ignore;
import org.junit.Test;
public class Base64OutputStreamTest {
Base64OutputStream output = new Base64OutputStream(Base64OutputStream.DEFAULT_ALPHABET,
Base64OutputStream.DEFAULT_PADDING, 4);
@Test
@Ignore
public void write() throws Exception {
ByteArrayOutputStream underlying = new ByteArrayOutputStream();
output.reset(underlying);
underlying.reset();
output.write("".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("", new String(underlying.toByteArray()));
underlying.reset();
output.write("f".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zg==", new String(underlying.toByteArray()));
underlying.reset();
output.write("fo".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zm8=", new String(underlying.toByteArray()));
underlying.reset();
output.write("foo".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zm9v", new String(underlying.toByteArray()));
underlying.reset();
output.write("foob".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zm9vYg==", new String(underlying.toByteArray()));
underlying.reset();
output.write("fooba".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zm9vYmE=", new String(underlying.toByteArray()));
underlying.reset();
output.write("foobar".getBytes(Charset.forName("utf-8")));
output.flush();
assertEquals("Zm9vYmFy", new String(underlying.toByteArray()));
}
@Test
public void realWorldError() throws Exception {
ByteArrayOutputStream underlying = new ByteArrayOutputStream();
long value = System.currentTimeMillis() + 1000;
Long.toString(value);
byte[] longBuffer = longToBytes(value);
output = new Base64OutputStream();
output.reset(underlying);
output.write("testuser".getBytes(Charset.forName("utf-8")));
output.write((byte) ';');
output.write(longBuffer);
output.write((byte) ';');
output.flush();
assertEquals("", new String(underlying.toByteArray()));
}
private long bytesToLong(final byte[] bytes) {
long tmp = 0;
tmp |= (long) (bytes[0] & 0xff) << 56;
tmp |= (long) (bytes[1] & 0xff) << 48;
tmp |= (long) (bytes[2] & 0xff) << 40;
tmp |= (long) (bytes[3] & 0xff) << 32;
tmp |= (long) (bytes[4] & 0xff) << 24;
tmp |= (long) (bytes[5] & 0xff) << 16;
tmp |= (long) (bytes[6] & 0xff) << 8;
tmp |= bytes[7] & 0xff;
return tmp;
}
private void printLong(final long value) {
byte[] longBuffer = longToBytes(value);
for (int i = 0; i < longBuffer.length; i++) {
System.out.print(printByte(longBuffer[i]) + '.');
}
System.out.println();
}
private byte[] longToBytes(final long value) {
byte[] longBuffer = new byte[8];
longBuffer[0] = (byte) ((value >> 56) & 0xff);
longBuffer[1] = (byte) ((value >> 48) & 0xff);
longBuffer[2] = (byte) ((value >> 40) & 0xff);
longBuffer[3] = (byte) ((value >> 32) & 0xff);
longBuffer[4] = (byte) ((value >> 24) & 0xff);
longBuffer[5] = (byte) ((value >> 16) & 0xff);
longBuffer[6] = (byte) ((value >> 8) & 0xff);
longBuffer[7] = (byte) (value & 0xff);
return longBuffer;
}
private String printByte(final byte b) {
StringBuilder builder = new StringBuilder();
builder.append((b & (byte) 128) == 128 ? '1' : '0');
builder.append((b & (byte) 64) == 64 ? '1' : '0');
builder.append((b & (byte) 32) == 32 ? '1' : '0');
builder.append((b & (byte) 16) == 16 ? '1' : '0');
builder.append((b & (byte) 8) == 8 ? '1' : '0');
builder.append((b & (byte) 4) == 4 ? '1' : '0');
builder.append((b & (byte) 2) == 2 ? '1' : '0');
builder.append((b & (byte) 1) == 1 ? '1' : '0');
return builder.toString();
}
@Test
@Ignore
public void serialWrite_SmallerBuffer() throws Exception {
serialWrite("foo", "bar", "Zm9vYmFy", 4);
}
@Test
@Ignore
public void serialWrite_BiggerBuffer() throws Exception {
serialWrite("foo", "bar", "Zm9vYmFy", 40);
}
@Test
@Ignore
public void serialWrite_SmallerBuffer_Async1() throws Exception {
serialWrite("fo", "obar", "Zm9vYmFy", 4);
}
@Test
@Ignore
public void serialWrite_BiggerBuffer_Async1() throws Exception {
serialWrite("fo", "obar", "Zm9vYmFy", 40);
}
@Test
@Ignore
public void serialWrite_SmallerBuffer_Async2() throws Exception {
serialWrite("foob", "ar", "Zm9vYmFy", 4);
}
@Test
@Ignore
public void serialWrite_BiggerBuffer_Async2() throws Exception {
serialWrite("foob", "ar", "Zm9vYmFy", 40);
}
public void serialWrite(final String src1, final String src2, final String dest, final int bufferSize)
throws Exception {
ByteArrayOutputStream underlying = new ByteArrayOutputStream();
byte[] data1 = src1.getBytes(Charset.forName("utf-8"));
byte[] data2 = src2.getBytes(Charset.forName("utf-8"));
output = new Base64OutputStream(Base64OutputStream.DEFAULT_ALPHABET, Base64OutputStream.DEFAULT_PADDING,
bufferSize);
output.reset(underlying);
output.write(data1);
output.write(data2);
output.flush();
assertEquals(dest, new String(underlying.toByteArray()));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment