* Code is based on Base64 and Base64OutputStream code from Commons-Codec 1.4.
*
* @see RFC 2045
*/
public class Base64OutputStream extends FilterOutputStream {
// Default line length per RFC 2045 section 6.8.
private static final int DEFAULT_LINE_LENGTH = 76;
// CRLF line separator per RFC 2045 section 2.1.
private static final byte[] CRLF_SEPARATOR = { '\r', '\n' };
// This array is a lookup table that translates 6-bit positive integer index
// values into their "Base64 Alphabet" equivalents as specified in Table 1
// of RFC 2045.
static final byte[] BASE64_TABLE = { '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', '+', '/' };
// Byte used to pad output.
private static final byte BASE64_PAD = '=';
// This set contains all base64 characters including the pad character. Used
// solely to check if a line separator contains any of these characters.
private static final Set
* The given line length will be rounded up to the nearest multiple of 4. If
* the line length is zero then the output will not be split into lines.
*
* @param out
* underlying output stream.
* @param lineLength
* desired line length.
*/
public Base64OutputStream(OutputStream out, int lineLength) {
this(out, lineLength, CRLF_SEPARATOR);
}
/**
* Creates a
* The given line length will be rounded up to the nearest multiple of 4. If
* the line length is zero then the output will not be split into lines and
* the line separator is ignored.
*
* The line separator must not include characters from the BASE64 alphabet
* (including the padding character Base64OutputStream
that writes the encoded data
* to the given output stream using the default line length (76) and line
* separator (CRLF).
*
* @param out
* underlying output stream.
*/
public Base64OutputStream(OutputStream out) {
this(out, DEFAULT_LINE_LENGTH, CRLF_SEPARATOR);
}
/**
* Creates a Base64OutputStream
that writes the encoded data
* to the given output stream using the given line length and the default
* line separator (CRLF).
* Base64OutputStream
that writes the encoded data
* to the given output stream using the given line length and line
* separator.
* =
).
*
* @param out
* underlying output stream.
* @param lineLength
* desired line length.
* @param lineSeparator
* line separator to use.
*/
public Base64OutputStream(OutputStream out, int lineLength,
byte[] lineSeparator) {
super(out);
if (out == null)
throw new IllegalArgumentException();
if (lineLength < 0)
throw new IllegalArgumentException();
checkLineSeparator(lineSeparator);
this.lineLength = lineLength;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0,
lineSeparator.length);
this.encoded = new byte[ENCODED_BUFFER_SIZE];
}
@Override
public final void write(final int b) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
singleByte[0] = (byte) b;
write0(singleByte, 0, 1);
}
@Override
public final void write(final byte[] buffer) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
if (buffer == null)
throw new NullPointerException();
if (buffer.length == 0)
return;
write0(buffer, 0, buffer.length);
}
@Override
public final void write(final byte[] buffer, final int offset,
final int length) throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
if (buffer == null)
throw new NullPointerException();
if (offset < 0 || length < 0 || offset + length > buffer.length)
throw new IndexOutOfBoundsException();
if (length == 0)
return;
write0(buffer, offset, offset + length);
}
@Override
public void flush() throws IOException {
if (closed)
throw new IOException("Base64OutputStream has been closed");
flush0();
}
@Override
public void close() throws IOException {
if (closed)
return;
closed = true;
close0();
}
private void write0(final byte[] buffer, final int from, final int to)
throws IOException {
for (int i = from; i < to; i++) {
data = (data << 8) | (buffer[i] & 0xff);
if (++modulus == 3) {
modulus = 0;
// write line separator if necessary
if (lineLength > 0 && linePosition >= lineLength) {
// writeLineSeparator() inlined for performance reasons
linePosition = 0;
if (encoded.length - position < lineSeparator.length)
flush0();
for (byte ls : lineSeparator)
encoded[position++] = ls;
}
// encode data into 4 bytes
if (encoded.length - position < 4)
flush0();
encoded[position++] = BASE64_TABLE[(data >> 18) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 12) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 6) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[data & MASK_6BITS];
linePosition += 4;
}
}
}
private void flush0() throws IOException {
if (position > 0) {
out.write(encoded, 0, position);
position = 0;
}
}
private void close0() throws IOException {
if (modulus != 0)
writePad();
// write line separator at the end of the encoded data
if (lineLength > 0 && linePosition > 0) {
writeLineSeparator();
}
flush0();
}
private void writePad() throws IOException {
// write line separator if necessary
if (lineLength > 0 && linePosition >= lineLength) {
writeLineSeparator();
}
// encode data into 4 bytes
if (encoded.length - position < 4)
flush0();
if (modulus == 1) {
encoded[position++] = BASE64_TABLE[(data >> 2) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data << 4) & MASK_6BITS];
encoded[position++] = BASE64_PAD;
encoded[position++] = BASE64_PAD;
} else {
assert modulus == 2;
encoded[position++] = BASE64_TABLE[(data >> 10) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data >> 4) & MASK_6BITS];
encoded[position++] = BASE64_TABLE[(data << 2) & MASK_6BITS];
encoded[position++] = BASE64_PAD;
}
linePosition += 4;
}
private void writeLineSeparator() throws IOException {
linePosition = 0;
if (encoded.length - position < lineSeparator.length)
flush0();
for (byte ls : lineSeparator)
encoded[position++] = ls;
}
private void checkLineSeparator(byte[] lineSeparator) {
if (lineSeparator.length > ENCODED_BUFFER_SIZE)
throw new IllegalArgumentException("line separator length exceeds "
+ ENCODED_BUFFER_SIZE);
for (byte b : lineSeparator) {
if (BASE64_CHARS.contains(b)) {
throw new IllegalArgumentException(
"line separator must not contain base64 character '"
+ (char) (b & 0xff) + "'");
}
}
}
}