io.undertow.util.FlexBase64 Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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 io.undertow.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
/**
* An efficient and flexible Base64 implementation.
*
* This class can deal with both MIME Base64 and Base64url.
*
* @author Jason T. Greene
*/
public class FlexBase64 {
/*
* Note that this code heavily favors performance over reuse and clean style.
*/
private static final byte[] STANDARD_ENCODING_TABLE;
private static final byte[] STANDARD_DECODING_TABLE = new byte[80];
private static final byte[] URL_ENCODING_TABLE;
private static final byte[] URL_DECODING_TABLE = new byte[80];
private static final Constructor STRING_CONSTRUCTOR;
static {
STANDARD_ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(StandardCharsets.US_ASCII);
URL_ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes(StandardCharsets.US_ASCII);
for (int i = 0; i < STANDARD_ENCODING_TABLE.length; i++) {
int v = (STANDARD_ENCODING_TABLE[i] & 0xFF) - 43;
STANDARD_DECODING_TABLE[v] = (byte)(i + 1); // zero = illegal
}
for (int i = 0; i < URL_ENCODING_TABLE.length; i++) {
int v = (URL_ENCODING_TABLE[i] & 0xFF) - 43;
URL_DECODING_TABLE[v] = (byte)(i + 1); // zero = illegal
}
Constructor c = null;
try {
PrivilegedExceptionAction> runnable = new PrivilegedExceptionAction>() {
@Override
public Constructor run() throws Exception {
Constructor c;
c = String.class.getDeclaredConstructor(char[].class, boolean.class);
c.setAccessible(true);
return c;
}
};
if (System.getSecurityManager() != null) {
c = AccessController.doPrivileged(runnable);
} else {
c = runnable.run();
}
} catch (Throwable t) {
}
STRING_CONSTRUCTOR = c;
}
/**
* Creates a state driven base64 encoder.
*
* The Encoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.
*
* @param wrap whether or not to wrap at 76 characters with CRLF
* @return an createEncoder instance
*/
public static Encoder createEncoder(boolean wrap) {
return new Encoder(wrap, false);
}
/**
* Creates a state driven base64url encoder.
*
* The Encoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.
*
* @param wrap whether or not to wrap at 76 characters with CRLF
* @return an createEncoder instance
*/
public static Encoder createURLEncoder(boolean wrap) {
return new Encoder(wrap, true);
}
/**
* Creates a state driven base64 decoder.
*
* The Decoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.
*
* @return a new createDecoder instance
*/
public static Decoder createDecoder() {
return new Decoder(false);
}
/**
* Creates a state driven base64url decoder.
*
* The Decoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.
*
* @return a new createDecoder instance
*/
public static Decoder createURLDecoder() {
return new Decoder(true);
}
/**
* Encodes a fixed and complete byte array into a Base64 String.
*
* This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
* instead.
*
* @param source the byte array to encode from
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64 output
*/
public static String encodeString(byte[] source, boolean wrap) {
return Encoder.encodeString(source, 0, source.length, wrap, false);
}
/**
* Encodes a fixed and complete byte array into a Base64url String.
*
*
This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
* instead.
*
* @param source the byte array to encode from
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64url output
*/
public static String encodeStringURL(byte[] source, boolean wrap) {
return Encoder.encodeString(source, 0, source.length, wrap, true);
}
/**
* Encodes a fixed and complete byte array into a Base64 String.
*
*
This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
*
*
* // Encodes "ell"
* FlexBase64.encodeString("hello".getBytes("US-ASCII"), 1, 4);
*
*
* @param source the byte array to encode from
* @param pos the position to start encoding from
* @param limit the position to halt encoding at (exclusive)
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64 output
*/
public static String encodeString(byte[] source, int pos, int limit, boolean wrap) {
return Encoder.encodeString(source, pos, limit, wrap, false);
}
/**
* Encodes a fixed and complete byte array into a Base64url String.
*
* This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
*
*
* // Encodes "ell"
* FlexBase64.encodeStringURL("hello".getBytes("US-ASCII"), 1, 4);
*
*
* @param source the byte array to encode from
* @param pos the position to start encoding from
* @param limit the position to halt encoding at (exclusive)
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64url output
*/
public static String encodeStringURL(byte[] source, int pos, int limit, boolean wrap) {
return Encoder.encodeString(source, pos, limit, wrap, true);
}
/**
* Encodes a fixed and complete byte buffer into a Base64 String.
*
* This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
*
*
* // Encodes "hello"
* FlexBase64.encodeString(ByteBuffer.wrap("hello".getBytes("US-ASCII")), false);
*
*
* @param source the byte buffer to encode from
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64 output
*/
public static String encodeString(ByteBuffer source, boolean wrap) {
return Encoder.encodeString(source, wrap, false);
}
/**
* Encodes a fixed and complete byte buffer into a Base64url String.
*
* This method is only useful for applications which require a String and have all data to be encoded up-front.
* Note that byte arrays or buffers are almost always a better storage choice. They consume half the memory and
* can be reused (modified). In other words, it is almost always better to use {@link #encodeBytes},
* {@link #createEncoder}, or {@link #createEncoderOutputStream} instead.
*
*
* // Encodes "hello"
* FlexBase64.encodeStringURL(ByteBuffer.wrap("hello".getBytes("US-ASCII")), false);
*
*
* @param source the byte buffer to encode from
* @param wrap whether or not to wrap the output at 76 chars with CRLFs
* @return a new String representing the Base64url output
*/
public static String encodeStringURL(ByteBuffer source, boolean wrap) {
return Encoder.encodeString(source, wrap, true);
}
/**
* Encodes a fixed and complete byte buffer into a Base64 byte array.
*
*
* // Encodes "ell"
* FlexBase64.encodeString("hello".getBytes("US-ASCII"), 1, 4, false);
*
*
* @param source the byte array to encode from
* @param pos the position to start encoding at
* @param limit the position to halt encoding at (exclusive)
* @param wrap whether or not to wrap at 76 characters with CRLFs
* @return a new byte array containing the encoded ASCII values
*/
public static byte[] encodeBytes(byte[] source, int pos, int limit, boolean wrap) {
return Encoder.encodeBytes(source, pos, limit, wrap, false);
}
/**
* Encodes a fixed and complete byte buffer into a Base64url byte array.
*
*
* // Encodes "ell"
* FlexBase64.encodeStringURL("hello".getBytes("US-ASCII"), 1, 4, false);
*
*
* @param source the byte array to encode from
* @param pos the position to start encoding at
* @param limit the position to halt encoding at (exclusive)
* @param wrap whether or not to wrap at 76 characters with CRLFs
* @return a new byte array containing the encoded ASCII values
*/
public static byte[] encodeBytesURL(byte[] source, int pos, int limit, boolean wrap) {
return Encoder.encodeBytes(source, pos, limit, wrap, true);
}
/**
* Decodes a Base64 encoded string into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64 string to decode
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decode(String source) throws IOException {
return Decoder.decode(source, false);
}
/**
* Decodes a Base64url encoded string into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64 string to decode
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decodeURL(String source) throws IOException {
return Decoder.decode(source, true);
}
/**
* Decodes a Base64 encoded byte buffer into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64 content to decode
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decode(ByteBuffer source) throws IOException {
return Decoder.decode(source, false);
}
/**
* Decodes a Base64url encoded byte buffer into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64 content to decode
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decodeURL(ByteBuffer source) throws IOException {
return Decoder.decode(source, true);
}
/**
* Decodes a Base64 encoded byte array into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64 content to decode
* @param off position to start decoding from in source
* @param limit position to stop decoding in source (exclusive)
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decode(byte[] source, int off, int limit) throws IOException {
return Decoder.decode(source, off, limit, false);
}
/**
* Decodes a Base64url encoded byte array into a new byte buffer. The returned byte buffer is a heap buffer,
* and it is therefor possible to retrieve the backing array using {@link java.nio.ByteBuffer#array()},
* {@link java.nio.ByteBuffer#arrayOffset()} and {@link java.nio.ByteBuffer#limit()}. The latter is very
* important since the decoded array may be larger than the decoded data. This is due to length estimation which
* avoids an unnecessary array copy.
*
* @param source the Base64url content to decode
* @param off position to start decoding from in source
* @param limit position to stop decoding in source (exclusive)
* @return a byte buffer containing the decoded output
* @throws IOException if the encoding is invalid or corrupted
*/
public static ByteBuffer decodeURL(byte[] source, int off, int limit) throws IOException {
return Decoder.decode(source, off, limit, true);
}
/**
* Creates an InputStream wrapper which encodes a source into base64 as it is read, until the source hits EOF.
* Upon hitting EOF, a standard base64 termination sequence will be readable. Clients can simply treat this input
* stream as if they were reading from a base64 encoded file. This stream attempts to read and encode in buffer
* size chunks from the source, in order to improve overall performance. Thus, BufferInputStream is not necessary
* and will lead to double buffering.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param source an input source to read from
* @param bufferSize the chunk size to buffer from the source
* @param wrap whether or not the stream should wrap base64 output at 76 characters
* @return an encoded input stream instance.
*/
public static EncoderInputStream createEncoderInputStream(InputStream source, int bufferSize, boolean wrap) {
return new EncoderInputStream(source, bufferSize, wrap, false);
}
/**
* Creates an InputStream wrapper which encodes a source into base64 as it is read, until the source hits EOF.
* Upon hitting EOF, a standard base64 termination sequence will be readable. Clients can simply treat this input
* stream as if they were reading from a base64 encoded file. This stream attempts to read and encode in 8192 byte
* chunks. Thus, BufferedInputStream is not necessary as a source and will lead to double buffering.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param source an input source to read from
* @return an encoded input stream instance.
*/
public static EncoderInputStream createEncoderInputStream(InputStream source) {
return new EncoderInputStream(source);
}
/**
* Creates an InputStream wrapper which decodes a base64 input source into the decoded content as it is read,
* until the source hits EOF. Upon hitting EOF, a standard base64 termination sequence will be readable.
* Clients can simply treat this input stream as if they were reading from a base64 encoded file. This stream
* attempts to read and encode in buffer size byte chunks. Thus, BufferedInputStream is not necessary
* as a source and will lead to double buffering.
*
* Note that the end of a base64 stream can not reliably be detected, so if multiple base64 streams exist on the
* wire, the source stream will need to simulate an EOF when the boundary mechanism is detected.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param source an input source to read from
* @param bufferSize the chunk size to buffer before when reading from the target
* @return a decoded input stream instance.
*/
public static DecoderInputStream createDecoderInputStream(InputStream source, int bufferSize) {
return new DecoderInputStream(source, bufferSize);
}
/**
* Creates an InputStream wrapper which decodes a base64 input source into the decoded content as it is read,
* until the source hits EOF. Upon hitting EOF, a standard base64 termination sequence will be readable.
* Clients can simply treat this input stream as if they were reading from a base64 encoded file. This stream
* attempts to read and encode in 8192 byte chunks. Thus, BufferedInputStream is not necessary
* as a source and will lead to double buffering.
*
* Note that the end of a base64 stream can not reliably be detected, so if multiple base64 streams exist on the
* wire, the source stream will need to simulate an EOF when the boundary mechanism is detected.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param source an input source to read from
* @return a decoded input stream instance.
*/
public static DecoderInputStream createDecoderInputStream(InputStream source) {
return new DecoderInputStream(source);
}
/**
* Creates an OutputStream wrapper which base64 encodes and writes to the passed OutputStream target. When this
* stream is closed base64 padding will be added if needed. Alternatively if this represents an "inner stream",
* the {@link FlexBase64.EncoderOutputStream#complete()} method can be called to close out
* the inner stream without closing the wrapped target.
*
* All bytes written will be queued to a buffer in the specified size. This stream, therefore, does not require
* BufferedOutputStream, which would lead to double buffering.
*
* @param target an output target to write to
* @param bufferSize the chunk size to buffer before writing to the target
* @param wrap whether or not the stream should wrap base64 output at 76 characters
* @return an encoded output stream instance.
*/
public static EncoderOutputStream createEncoderOutputStream(OutputStream target, int bufferSize, boolean wrap) {
return new EncoderOutputStream(target, bufferSize, wrap);
}
/**
* Creates an OutputStream wrapper which base64 encodes and writes to the passed OutputStream target. When this
* stream is closed base64 padding will be added if needed. Alternatively if this represents an "inner stream",
* the {@link FlexBase64.EncoderOutputStream#complete()} method can be called to close out
* the inner stream without closing the wrapped target.
*
*
All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does not require
* BufferedOutputStream, which would lead to double buffering.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param output the output stream to write encoded output to
* @return an encoded output stream instance.
*/
public static EncoderOutputStream createEncoderOutputStream(OutputStream output) {
return new EncoderOutputStream(output);
}
/**
* Creates an OutputStream wrapper which decodes base64 content before writing to the passed OutputStream target.
*
* All bytes written will be queued to a buffer using the specified buffer size. This stream, therefore, does
* not require BufferedOutputStream, which would lead to double buffering.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param output the output stream to write decoded output to
* @param bufferSize the buffer size to buffer writes to
* @return a decoded output stream instance.
*/
public static DecoderOutputStream createDecoderOutputStream(OutputStream output, int bufferSize) {
return new DecoderOutputStream(output, bufferSize);
}
/**
* Creates an OutputStream wrapper which decodes base64 content before writing to the passed OutputStream target.
*
* All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does
* not require BufferedOutputStream, which would lead to double buffering.
*
* This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.
*
* @param output the output stream to write decoded output to
* @return a decoded output stream instance.
*/
public static DecoderOutputStream createDecoderOutputStream(OutputStream output) {
return new DecoderOutputStream(output);
}
/**
* Controls the encoding process.
*/
public static final class Encoder {
private int state;
private int last;
private int count;
private final boolean wrap;
private int lastPos;
private final byte[] encodingTable;
private Encoder(boolean wrap, boolean url) {
this.wrap = wrap;
this.encodingTable = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE;
}
/**
* Encodes bytes read from source and writes them in base64 format to target. If the source limit is hit, this
* method will return and save the current state, such that future calls can resume the encoding process.
* In addition, if the target does not have the capacity to fit an entire quad of bytes, this method will also
* return and save state for subsequent calls to this method. Once all bytes have been encoded to the target,
* {@link #complete(java.nio.ByteBuffer)} should be called to add the necessary padding characters.
*
* @param source the byte buffer to read from
* @param target the byte buffer to write to
*/
public void encode(ByteBuffer source, ByteBuffer target) {
if (target == null)
throw new IllegalStateException();
int last = this.last;
int state = this.state;
boolean wrap = this.wrap;
int count = this.count;
final byte[] ENCODING_TABLE = encodingTable;
int remaining = source.remaining();
while (remaining > 0) {
// Unrolled state machine for performance (resumes and executes all states in one iteration)
int require = 4 - state;
require = wrap && (count >= 72) ? require + 2 : require;
if (target.remaining() < require) {
break;
}
// ( 6 | 2) (4 | 4) (2 | 6)
int b = source.get() & 0xFF;
if (state == 0) {
target.put(ENCODING_TABLE[b >>> 2]);
last = (b & 0x3) << 4;
state++;
if (--remaining <= 0) {
break;
}
b = source.get() & 0xFF;
}
if (state == 1) {
target.put(ENCODING_TABLE[last | (b >>> 4)]);
last = (b & 0x0F) << 2;
state++;
if (--remaining <= 0) {
break;
}
b = source.get() & 0xFF;
}
if (state == 2) {
target.put(ENCODING_TABLE[last | (b >>> 6)]);
target.put(ENCODING_TABLE[b & 0x3F]);
last = state = 0;
remaining--;
}
if (wrap) {
count += 4;
if (count >= 76) {
count = 0;
target.putShort((short)0x0D0A);
}
}
}
this.count = count;
this.last = last;
this.state = state;
this.lastPos = source.position();
}
/**
* Encodes bytes read from source and writes them in base64 format to target. If the source limit is hit, this
* method will return and save the current state, such that future calls can resume the encoding process.
* In addition, if the target does not have the capacity to fit an entire quad of bytes, this method will also
* return and save state for subsequent calls to this method. Once all bytes have been encoded to the target,
* {@link #complete(byte[], int)} should be called to add the necessary padding characters. In order to
* determine the last read position, the {@link #getLastInputPosition()} can be used.
*
* Note that the limit values are not lengths, they are positions similar to
* {@link java.nio.ByteBuffer#limit()}. To calculate a length simply subtract position from limit.
*
*
* Encoder encoder = FlexBase64.createEncoder(false);
* byte[] outBuffer = new byte[10];
* // Encode "ell"
* int outPosition = encoder.encode("hello".getBytes("US-ASCII"), 1, 4, outBuffer, 5, 10);
* // Prints "9 : ZWxs"
* System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
*
*
* @param source the byte array to read from
* @param pos ths position in the byte array to start reading from
* @param limit the position in the byte array that is after the end of the source data
* @param target the byte array to write base64 bytes to
* @param opos the position to start writing to the target array at
* @param olimit the position in the target byte array that makes the end of the writable area (exclusive)
* @return the position in the target array immediately following the last byte written
*/
public int encode(byte[] source, int pos, int limit, byte[] target, int opos, int olimit) {
if (target == null)
throw new IllegalStateException();
int last = this.last;
int state = this.state;
int count = this.count;
boolean wrap = this.wrap;
final byte[] ENCODING_TABLE = encodingTable;
while (limit > pos) {
// Unrolled state machine for performance (resumes and executes all states in one iteration)
int require = 4 - state;
require = wrap && count >= 72 ? require + 2 : require;
if ((require + opos) > olimit) {
break;
}
// ( 6 | 2) (4 | 4) (2 | 6)
int b = source[pos++] & 0xFF;
if (state == 0) {
target[opos++] = ENCODING_TABLE[b >>> 2];
last = (b & 0x3) << 4;
state++;
if (pos >= limit) {
break;
}
b = source[pos++] & 0xFF;
}
if (state == 1) {
target[opos++] = ENCODING_TABLE[last | (b >>> 4)];
last = (b & 0x0F) << 2;
state++;
if (pos >= limit) {
break;
}
b = source[pos++] & 0xFF;
}
if (state == 2) {
target[opos++] = ENCODING_TABLE[last | (b >>> 6)];
target[opos++] = ENCODING_TABLE[b & 0x3F];
last = state = 0;
}
if (wrap) {
count += 4;
if (count >= 76) {
count = 0;
target[opos++] = 0x0D;
target[opos++] = 0x0A;
}
}
}
this.count = count;
this.last = last;
this.state = state;
this.lastPos = pos;
return opos;
}
private static String encodeString(byte[] source, int pos, int limit, boolean wrap, boolean url) {
int olimit = (limit - pos);
int remainder = olimit % 3;
olimit = (olimit + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4;
olimit += (wrap ? (olimit / 76) * 2 + 2 : 0);
char[] target = new char[olimit];
int opos = 0;
int last = 0;
int count = 0;
int state = 0;
final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE;
while (limit > pos) {
// ( 6 | 2) (4 | 4) (2 | 6)
int b = source[pos++] & 0xFF;
target[opos++] = (char) ENCODING_TABLE[b >>> 2];
last = (b & 0x3) << 4;
if (pos >= limit) {
state = 1;
break;
}
b = source[pos++] & 0xFF;
target[opos++] = (char) ENCODING_TABLE[last | (b >>> 4)];
last = (b & 0x0F) << 2;
if (pos >= limit) {
state = 2;
break;
}
b = source[pos++] & 0xFF;
target[opos++] = (char) ENCODING_TABLE[last | (b >>> 6)];
target[opos++] = (char) ENCODING_TABLE[b & 0x3F];
if (wrap) {
count += 4;
if (count >= 76) {
count = 0;
target[opos++] = 0x0D;
target[opos++] = 0x0A;
}
}
}
complete(target, opos, state, last, wrap, url);
try {
// Eliminate copying on Open/Oracle JDK
if (STRING_CONSTRUCTOR != null) {
return STRING_CONSTRUCTOR.newInstance(target, Boolean.TRUE);
}
} catch (Exception e) {
// Ignoring on purpose
}
return new String(target);
}
private static byte[] encodeBytes(byte[] source, int pos, int limit, boolean wrap, boolean url) {
int olimit = (limit - pos);
int remainder = olimit % 3;
olimit = (olimit + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4;
olimit += (wrap ? (olimit / 76) * 2 + 2 : 0);
byte[] target = new byte[olimit];
int opos = 0;
int count = 0;
int last = 0;
int state = 0;
final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE;
while (limit > pos) {
// ( 6 | 2) (4 | 4) (2 | 6)
int b = source[pos++] & 0xFF;
target[opos++] = ENCODING_TABLE[b >>> 2];
last = (b & 0x3) << 4;
if (pos >= limit) {
state = 1;
break;
}
b = source[pos++] & 0xFF;
target[opos++] = ENCODING_TABLE[last | (b >>> 4)];
last = (b & 0x0F) << 2;
if (pos >= limit) {
state = 2;
break;
}
b = source[pos++] & 0xFF;
target[opos++] = ENCODING_TABLE[last | (b >>> 6)];
target[opos++] = ENCODING_TABLE[b & 0x3F];
if (wrap) {
count += 4;
if (count >= 76) {
count = 0;
target[opos++] = 0x0D;
target[opos++] = 0x0A;
}
}
}
complete(target, opos, state, last, wrap, url);
return target;
}
private static String encodeString(ByteBuffer source, boolean wrap, boolean url) {
int remaining = source.remaining();
int remainder = remaining % 3;
int olimit = (remaining + (remainder == 0 ? 0 : 3 - remainder)) / 3 * 4;
olimit += (wrap ? olimit / 76 * 2 + 2 : 0);
char[] target = new char[olimit];
int opos = 0;
int last = 0;
int state = 0;
int count = 0;
final byte[] ENCODING_TABLE = url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE;
while (remaining > 0) {
// ( 6 | 2) (4 | 4) (2 | 6)
int b = source.get() & 0xFF;
target[opos++] = (char) ENCODING_TABLE[b >>> 2];
last = (b & 0x3) << 4;
if (--remaining <= 0) {
state = 1;
break;
}
b = source.get() & 0xFF;
target[opos++] = (char) ENCODING_TABLE[last | (b >>> 4)];
last = (b & 0x0F) << 2;
if (--remaining <= 0) {
state = 2;
break;
}
b = source.get() & 0xFF;
target[opos++] = (char) ENCODING_TABLE[last | (b >>> 6)];
target[opos++] = (char) ENCODING_TABLE[b & 0x3F];
remaining--;
if (wrap) {
count += 4;
if (count >= 76) {
count = 0;
target[opos++] = 0x0D;
target[opos++] = 0x0A;
}
}
}
complete(target, opos, state, last, wrap, url);
try {
// Eliminate copying on Open/Oracle JDK
if (STRING_CONSTRUCTOR != null) {
return STRING_CONSTRUCTOR.newInstance(target, Boolean.TRUE);
}
} catch (Exception e) {
// Ignoring on purpose
}
return new String(target);
}
/**
* Gets the last position where encoding left off in the last byte array that was used.
* If the target for encoded content does not have the necessary capacity, this method should be used to
* determine where to start from on subsequent reads.
*
* @return the last known read position
*/
public int getLastInputPosition() {
return lastPos;
}
/**
* Completes an encoding session by writing out the necessary padding. This is essential to complying
* with the Base64 format. This method will write at most 4 or 2 bytes starting at pos,depending on
* whether or not wrapping is enabled.
*
*
* Encoder encoder = FlexBase64.createEncoder(false);
* byte[] outBuffer = new byte[13];
*
* // Encodes "ello"
* int outPosition = encoder.encode("hello".getBytes("US-ASCII"), 0, 4, outBuffer, 5, 13);
* outPosition = encoder.complete(outBuffer, outPosition);
*
* // Prints "13 : aGVsbA=="
* System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
*
*
* @param target the byte array to write to
* @param pos the position to start writing at
* @return the position after the last byte written
*/
public int complete(byte[] target, int pos) {
if (state > 0) {
target[pos++] = encodingTable[last];
for (int i = state; i < 3; i++) {
target[pos++] = (byte)'=';
}
last = state = 0;
}
if (wrap) {
target[pos++] = 0x0D;
target[pos++] = 0x0A;
}
return pos;
}
private static int complete(char[] target, int pos, int state, int last, boolean wrap, boolean url) {
if (state > 0) {
target[pos++] = (char) (url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE)[last];
for (int i = state; i < 3; i++) {
target[pos++] = '=';
}
}
if (wrap) {
target[pos++] = 0x0D;
target[pos++] = 0x0A;
}
return pos;
}
private static int complete(byte[] target, int pos, int state, int last, boolean wrap, boolean url) {
if (state > 0) {
target[pos++] = (url ? URL_ENCODING_TABLE : STANDARD_ENCODING_TABLE)[last];
for (int i = state; i < 3; i++) {
target[pos++] = '=';
}
}
if (wrap) {
target[pos++] = 0x0D;
target[pos++] = 0x0A;
}
return pos;
}
/**
* Completes an encoding session by writing out the necessary padding. This is essential to complying
* with the Base64 format. This method will write at most 4 or 2 bytes, depending on whether or not wrapping
* is enabled.
*
* @param target the byte buffer to write to
*/
public void complete(ByteBuffer target) {
if (state > 0) {
target.put(encodingTable[last]);
for (int i = state; i < 3; i++) {
target.put((byte)'=');
}
last = state = 0;
}
if (wrap) {
target.putShort((short)0x0D0A);
}
count = 0;
}
}
/**
* Controls the decoding process.
*/
public static final class Decoder {
private int state;
private int last;
private int lastPos;
private final byte[] decodingTable;
private static final int SKIP = 0x0FD00;
private static final int MARK = 0x0FE00;
private static final int DONE = 0x0FF00;
private static final int ERROR = 0xF0000;
private Decoder(boolean url) {
this.decodingTable = url ? URL_DECODING_TABLE : STANDARD_DECODING_TABLE;
}
private int nextByte(ByteBuffer buffer, int state, int last, boolean ignoreErrors) throws IOException {
return nextByte(buffer.get() & 0xFF, state, last, ignoreErrors);
}
private int nextByte(Object source, int pos, int state, int last, boolean ignoreErrors) throws IOException {
int c;
if (source instanceof byte[]) {
c = ((byte[])source)[pos] & 0xFF;
} else if (source instanceof String) {
c = ((String)source).charAt(pos) & 0xFF;
} else {
throw new IllegalArgumentException();
}
return nextByte(c, state, last, ignoreErrors);
}
private int nextByte(int c, int state, int last, boolean ignoreErrors) throws IOException {
if (last == MARK) {
if (c != '=') {
throw new IOException("Expected padding character");
}
return DONE;
}
if (c == '=') {
if (state == 2) {
return MARK;
} else if (state == 3) {
return DONE;
} else {
throw new IOException("Unexpected padding character");
}
}
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
return SKIP;
}
if (c < 43 || c > 122) {
if (ignoreErrors) {
return ERROR;
}
throw new IOException("Invalid base64 character encountered: " + c);
}
int b = (decodingTable[c - 43] & 0xFF) - 1;
if (b < 0) {
if (ignoreErrors) {
return ERROR;
}
throw new IOException("Invalid base64 character encountered: " + c);
}
return b;
}
/**
* Decodes one Base64 byte buffer into another. This method will return and save state
* if the target does not have the required capacity. Subsequent calls with a new target will
* resume reading where it last left off (the source buffer's position). Similarly not all of the
* source data need be available, this method can be repetitively called as data is made available.
*
* The decoder will skip white space, but will error if it detects corruption.
*
* @param source the byte buffer to read encoded data from
* @param target the byte buffer to write decoded data to
* @throws IOException if the encoded data is corrupted
*/
public void decode(ByteBuffer source, ByteBuffer target) throws IOException {
if (target == null)
throw new IllegalStateException();
int last = this.last;
int state = this.state;
int remaining = source.remaining();
int targetRemaining = target.remaining();
int b = 0;
while (remaining-- > 0 && targetRemaining > 0) {
b = nextByte(source, state, last, false);
if (b == MARK) {
last = MARK;
if (--remaining <= 0) {
break;
}
b = nextByte(source, state, last, false);
}
if (b == DONE) {
last = state = 0;
break;
}
if (b == SKIP) {
continue;
}
// ( 6 | 2) (4 | 4) (2 | 6)
if (state == 0) {
last = b << 2;
state++;
if (remaining-- <= 0) {
break;
}
b = nextByte(source, state, last, false);
if ((b & 0xF000) != 0) {
source.position(source.position() - 1);
continue;
}
}
if (state == 1) {
target.put((byte)(last | (b >>> 4)));
last = (b & 0x0F) << 4;
state++;
if (remaining-- <= 0 || --targetRemaining <= 0) {
break;
}
b = nextByte(source, state, last, false);
if ((b & 0xF000) != 0) {
source.position(source.position() - 1);
continue;
}
}
if (state == 2) {
target.put((byte) (last | (b >>> 2)));
last = (b & 0x3) << 6;
state++;
if (remaining-- <= 0 || --targetRemaining <= 0) {
break;
}
b = nextByte(source, state, last, false);
if ((b & 0xF000) != 0) {
source.position(source.position() - 1);
continue;
}
}
if (state == 3) {
target.put((byte)(last | b));
last = state = 0;
targetRemaining--;
}
}
if (remaining > 0) {
drain(source, b, state, last);
}
this.last = last;
this.state = state;
this.lastPos = source.position();
}
private void drain(ByteBuffer source, int b, int state, int last) {
while (b != DONE && source.remaining() > 0) {
try {
b = nextByte(source, state, last, true);
} catch (IOException e) {
b = 0;
}
if (b == MARK) {
last = MARK;
continue;
}
// Not WS/pad
if ((b & 0xF000) == 0) {
source.position(source.position() - 1);
break;
}
}
if (b == DONE) {
// SKIP one line of trailing whitespace
while (source.remaining() > 0) {
b = source.get();
if (b == '\n') {
break;
} else if (b != ' ' && b != '\t' && b != '\r') {
source.position(source.position() - 1);
break;
}
}
}
}
private int drain(Object source, int pos, int limit, int b, int state, int last) {
while (b != DONE && limit > pos) {
try {
b = nextByte(source, pos++, state, last, true);
} catch (IOException e) {
b = 0;
}
if (b == MARK) {
last = MARK;
continue;
}
// Not WS/pad
if ((b & 0xF000) == 0) {
pos--;
break;
}
}
if (b == DONE) {
// SKIP one line of trailing whitespace
while (limit > pos) {
if (source instanceof byte[]) {
b = ((byte[])source)[pos++] & 0xFF;
} else if (source instanceof String) {
b = ((String)source).charAt(pos++) & 0xFF;
} else {
throw new IllegalArgumentException();
}
if (b == '\n') {
break;
} else if (b != ' ' && b != '\t' && b != '\r') {
pos--;
break;
}
}
}
return pos;
}
private int decode(Object source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException {
if (target == null)
throw new IllegalStateException();
int last = this.last;
int state = this.state;
int pos = sourcePos;
int opos = targetPos;
int limit = sourceLimit;
int olimit = targetLimit;
int b = 0;
while (limit > pos && olimit > opos) {
b = nextByte(source, pos++, state, last, false);
if (b == MARK) {
last = MARK;
if (pos >= limit) {
break;
}
b = nextByte(source, pos++, state, last, false);
}
if (b == DONE) {
last = state = 0;
break;
}
if (b == SKIP) {
continue;
}
// ( 6 | 2) (4 | 4) (2 | 6)
if (state == 0) {
last = b << 2;
state++;
if (pos >= limit) {
break;
}
b = nextByte(source, pos++, state, last, false);
if ((b & 0xF000) != 0) {
pos--;
continue;
}
}
if (state == 1) {
target[opos++] = ((byte)(last | (b >>> 4)));
last = (b & 0x0F) << 4;
state++;
if (pos >= limit || opos >= olimit) {
break;
}
b = nextByte(source, pos++, state, last, false);
if ((b & 0xF000) != 0) {
pos--;
continue;
}
}
if (state == 2) {
target[opos++] = ((byte) (last | (b >>> 2)));
last = (b & 0x3) << 6;
state++;
if (pos >= limit || opos >= olimit) {
break;
}
b = nextByte(source, pos++, state, last, false);
if ((b & 0xF000) != 0) {
pos--;
continue;
}
}
if (state == 3) {
target[opos++] = ((byte)(last | b));
last = state = 0;
}
}
if (limit > pos) {
pos = drain(source, pos, limit, b, state, last);
}
this.last = last;
this.state = state;
this.lastPos = pos;
return opos;
}
/**
* Gets the last position where decoding left off in the last byte array that was used for reading.
* If the target for decoded content does not have the necessary capacity, this method should be used to
* determine where to start from on subsequent decode calls.
*
* @return the last known read position
*/
public int getLastInputPosition() {
return lastPos;
}
/**
* Decodes one Base64 byte array into another byte array. If the source limit is hit, this method will
* return and save the current state, such that future calls can resume the decoding process. Likewise,
* if the target does not have the capacity, this method will also return and save state for subsequent
* calls to this method.
*
* When multiple calls are made, {@link #getLastInputPosition()} should be used to determine what value
* should be set for sourcePos. Likewise, the returned target position should be used as the targetPos
* in a subsequent call.
*
* The decoder will skip white space, but will error if it detects corruption.
*
* @param source a Base64 encoded string to decode data from
* @param sourcePos the position in the source array to start decoding from
* @param sourceLimit the position in the source array to halt decoding when hit (exclusive)
* @param target the byte buffer to write decoded data to
* @param targetPos the position in the target byte array to begin writing at
* @param targetLimit the position in the target byte array to halt writing (exclusive)
* @throws IOException if the encoded data is corrupted
* @return the position in the target array immediately following the last byte written
*
*/
public int decode(String source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException {
return decode((Object)source, sourcePos, sourceLimit, target, targetPos, targetLimit);
}
/**
* Decodes a Base64 encoded string into the passed byte array. This method will return and save state
* if the target does not have the required capacity. Subsequent calls with a new target will
* resume reading where it last left off (the source buffer's position). Similarly not all of the
* source data need be available, this method can be repetitively called as data is made available.
*
* Since this method variant assumes a position of 0 and a limit of the item length,
* repeated calls will need fresh source and target values. {@link #decode(String, int, int, byte[], int, int)}
* would be a better fit if you need reuse
*
* The decoder will skip white space, but will error if it detects corruption.
*
* @param source a base64 encoded string to decode from
* @param target a byte array to write to
* @throws java.io.IOException if the base64 content is malformed
* @return output position following the last written byte
*/
public int decode(String source, byte[] target) throws IOException {
return decode(source, 0, source.length(), target, 0, target.length);
}
/**
* Decodes one Base64 byte array into another byte array. If the source limit is hit, this method will
* return and save the current state, such that future calls can resume the decoding process. Likewise,
* if the target does not have the capacity, this method will also return and save state for subsequent
* calls to this method.
*
* When multiple calls are made, {@link #getLastInputPosition()} should be used to determine what value
* should be set for sourcePos. Likewise, the returned target position should be used as the targetPos
* in a subsequent call.
*
* The decoder will skip white space, but will error if it detects corruption.
*
*
* Decoder decoder = FlexBase64.createDecoder();
* byte[] outBuffer = new byte[10];
* byte[] bytes = "aGVsbG8=".getBytes("US-ASCII");
* // Decode only 2 bytes
* int outPosition = decoder.decode(bytes, 0, 8, outBuffer, 5, 7);
* // Resume where we left off and get the rest
* outPosition = decoder.decode(bytes, decoder.getLastInputPosition(), 8, outBuffer, outPosition, 10);
* // Prints "10 : Hello"
* System.out.println(outPosition + " : " + new String(outBuffer, 0, 5, outPosition - 5));
*
*
*
* @param source the byte array to read encoded data from
* @param sourcePos the position in the source array to start decoding from
* @param sourceLimit the position in the source array to halt decoding when hit (exclusive)
* @param target the byte buffer to write decoded data to
* @param targetPos the position in the target byte array to begin writing at
* @param targetLimit the position in the target byte array to halt writing (exclusive)
* @throws IOException if the encoded data is corrupted
* @return the position in the target array immediately following the last byte written
*/
public int decode(byte[] source, int sourcePos, int sourceLimit, byte[] target, int targetPos, int targetLimit) throws IOException {
return decode((Object)source, sourcePos, sourceLimit, target, targetPos, targetLimit);
}
private static ByteBuffer decode(String source, boolean url) throws IOException {
int remainder = source.length() % 4;
int size = ((source.length() / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3;
byte[] buffer = new byte[size];
int actual = new Decoder(url).decode(source, 0, source.length(), buffer, 0, size);
return ByteBuffer.wrap(buffer, 0, actual);
}
private static ByteBuffer decode(byte[] source, int off, int limit, boolean url) throws IOException {
int len = limit - off;
int remainder = len % 4;
int size = ((len / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3;
byte[] buffer = new byte[size];
int actual = new Decoder(url).decode(source, off, limit, buffer, 0, size);
return ByteBuffer.wrap(buffer, 0, actual);
}
private static ByteBuffer decode(ByteBuffer source, boolean url) throws IOException {
int len = source.remaining();
int remainder = len % 4;
int size = ((len / 4) + (remainder == 0 ? 0 : 4 - remainder)) * 3;
ByteBuffer buffer = ByteBuffer.allocate(size);
new Decoder(url).decode(source, buffer);
buffer.flip();
return buffer;
}
}
/**
* An input stream which decodes bytes as they are read from a stream with Base64 encoded data.
*/
public static class DecoderInputStream extends InputStream {
private final InputStream input;
private final byte[] buffer;
private final Decoder decoder = createDecoder();
private int pos = 0;
private int limit = 0;
private byte[] one;
private DecoderInputStream(InputStream input) {
this(input, 8192);
}
private DecoderInputStream(InputStream input, int bufferSize) {
this.input = input;
buffer = new byte[bufferSize];
}
private int fill() throws IOException {
byte[] buffer = this.buffer;
int read = input.read(buffer, 0, buffer.length);
pos = 0;
limit = read;
return read;
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
for (;;) {
byte[] source = buffer;
int pos = this.pos;
int limit = this.limit;
boolean setPos = true;
if (pos >= limit) {
if (len > source.length) {
source = new byte[len];
limit = input.read(source, 0, len);
pos = 0;
setPos = false;
} else {
limit = fill();
pos = 0;
}
if (limit == -1) {
return -1;
}
}
int requested = len + pos;
limit = limit > requested ? requested : limit;
int read = decoder.decode(source, pos, limit, b, off, off+len) - off;
if (setPos) {
this.pos = decoder.getLastInputPosition();
}
if (read > 0) {
return read;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException {
byte[] one = this.one;
if (one == null) {
one = this.one = new byte[1];
}
int read = this.read(one, 0, 1);
return read > 0 ? one[0] & 0xFF : -1;
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
input.close();
}
}
/**
* An input stream which encodes bytes as they are read from a stream.
*/
public static class EncoderInputStream extends InputStream {
private final InputStream input;
private final byte[] buffer;
private final byte[] overflow = new byte[6];
private int overflowPos;
private int overflowLimit;
private final Encoder encoder;
private int pos = 0;
private int limit = 0;
private byte[] one;
private boolean complete;
private EncoderInputStream(InputStream input) {
this(input, 8192, true, false);
}
private EncoderInputStream(InputStream input, int bufferSize, boolean wrap, boolean url) {
this.input = input;
buffer = new byte[bufferSize];
this.encoder = new Encoder(wrap, url);
}
private int fill() throws IOException {
byte[] buffer = this.buffer;
int read = input.read(buffer, 0, buffer.length);
pos = 0;
limit = read;
return read;
}
/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException {
byte[] one = this.one;
if (one == null) {
one = this.one = new byte[1];
}
int read = this.read(one, 0, 1);
return read > 0 ? one[0] & 0xFF : -1;
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
byte[] buffer = this.buffer;
byte[] overflow = this.overflow;
int overflowPos = this.overflowPos;
int overflowLimit = this.overflowLimit;
boolean complete = this.complete;
boolean wrap = encoder.wrap;
int copy = 0;
if (overflowPos < overflowLimit) {
copy = copyOverflow(b, off, len, overflow, overflowPos, overflowLimit);
if (len <= copy || complete) {
return copy;
}
len -= copy;
off += copy;
} else if (complete) {
return -1;
}
for (;;) {
byte[] source = buffer;
int pos = this.pos;
int limit = this.limit;
boolean setPos = true;
if (pos >= limit) {
if (len > source.length) {
// If requested length exceeds buffer, allocate a new temporary buffer that will be
// one block less than an exact encoded output. This is to handle partial quad carryover
// from an earlier read.
int adjust = (len / 4 * 3) - 3;
if (wrap) {
adjust -= adjust / 76 * 2 + 2;
}
source = new byte[adjust];
limit = input.read(source, 0, adjust);
pos = 0;
setPos = false;
} else {
limit = fill();
pos = 0;
}
if (limit <= 0) {
this.complete = true;
if (len < (wrap ? 4 : 2)) {
overflowLimit = encoder.complete(overflow, 0);
this.overflowLimit = overflowLimit;
int ret = copyOverflow(b, off, len, overflow, 0, overflowLimit) + copy;
return ret == 0 ? -1 : ret;
}
int ret = encoder.complete(b, off) - off + copy;
return ret == 0 ? -1 : ret;
}
}
if (len < (wrap ? 6 : 4)) {
overflowLimit = encoder.encode(source, pos, limit, overflow, 0, overflow.length);
this.overflowLimit = overflowLimit;
this.pos = encoder.getLastInputPosition();
return copyOverflow(b, off, len, overflow, 0, overflowLimit) + copy;
}
int read = encoder.encode(source, pos, limit, b, off, off+len) - off;
if (setPos) {
this.pos = encoder.getLastInputPosition();
}
if (read > 0) {
return read + copy;
}
}
}
private int copyOverflow(byte[] b, int off, int len, byte[] overflow, int pos, int limit) {
limit -= pos;
len = limit <= len ? limit : len;
System.arraycopy(overflow, pos, b, off, len);
this.overflowPos = pos + len;
return len;
}
}
/**
* An output stream which base64 encodes all passed data and writes it to the wrapped target output stream.
*
* Closing this stream will result in the correct padding sequence being written. However, as
* required by the OutputStream contract, the wrapped stream will also be closed. If this is not desired,
* the {@link #complete()} method should be used.
*/
public static class EncoderOutputStream extends OutputStream {
private final OutputStream output;
private final byte[] buffer;
private final Encoder encoder;
private int pos = 0;
private byte[] one;
private EncoderOutputStream(OutputStream output) {
this(output, 8192, true);
}
private EncoderOutputStream(OutputStream output, int bufferSize, boolean wrap) {
this.output = output;
this.buffer = new byte[bufferSize];
this.encoder = createEncoder(wrap);
}
/**
* {@inheritDoc}
*/
@Override
public void write(byte[] b, int off, int len) throws IOException {
byte[] buffer = this.buffer;
Encoder encoder = this.encoder;
int pos = this.pos;
int limit = off + len;
int ipos = off;
while (ipos < limit) {
pos = encoder.encode(b, ipos, limit, buffer, pos, buffer.length);
int last = encoder.getLastInputPosition();
if (last == ipos || pos >= buffer.length) {
output.write(buffer, 0, pos);
pos = 0;
}
ipos = last;
}
this.pos = pos;
}
/**
* {@inheritDoc}
*/
@Override
public void write(int b) throws IOException {
byte[] one = this.one;
if (one == null) {
this.one = one = new byte[1];
}
one[0] = (byte)b;
write(one, 0, 1);
}
/**
* {@inheritDoc}
*/
@Override
public void flush() throws IOException {
OutputStream output = this.output;
output.write(buffer, 0, pos);
output.flush();
}
/**
* Completes the stream, writing out base64 padding characters if needed.
*
* @throws IOException if the underlying stream throws one
*/
public void complete() throws IOException {
OutputStream output = this.output;
byte[] buffer = this.buffer;
int pos = this.pos;
boolean completed = false;
if (buffer.length - pos >= (encoder.wrap ? 2 : 4)) {
this.pos = encoder.complete(buffer, pos);
completed = true;
}
flush();
if (!completed) {
int len = encoder.complete(buffer, 0);
output.write(buffer, 0, len);
output.flush();
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
try {
complete();
} catch (IOException e) {
// eat
}
try {
output.flush();
} catch (IOException e) {
// eat
}
output.close();
}
}
/**
* An output stream which decodes base64 data written to it, and writes the decoded output to the
* wrapped inner stream.
*/
public static class DecoderOutputStream extends OutputStream {
private final OutputStream output;
private final byte[] buffer;
private final Decoder decoder;
private int pos = 0;
private byte[] one;
private DecoderOutputStream(OutputStream output) {
this(output, 8192);
}
private DecoderOutputStream(OutputStream output, int bufferSize) {
this.output = output;
this.buffer = new byte[bufferSize];
this.decoder = createDecoder();
}
/**
* {@inheritDoc}
*/
@Override
public void write(byte[] b, int off, int len) throws IOException {
byte[] buffer = this.buffer;
Decoder decoder = this.decoder;
int pos = this.pos;
int limit = off + len;
int ipos = off;
while (ipos < limit) {
pos = decoder.decode(b, ipos, limit, buffer, pos, buffer.length);
int last = decoder.getLastInputPosition();
if (last == ipos || pos >= buffer.length) {
output.write(buffer, 0, pos);
pos = 0;
}
ipos = last;
}
this.pos = pos;
}
/**
* {@inheritDoc}
*/
@Override
public void write(int b) throws IOException {
byte[] one = this.one;
if (one == null) {
this.one = one = new byte[1];
}
one[0] = (byte)b;
write(one, 0, 1);
}
/**
* {@inheritDoc}
*/
@Override
public void flush() throws IOException {
OutputStream output = this.output;
output.write(buffer, 0, pos);
output.flush();
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
try {
flush();
} catch (IOException e) {
// eat
}
try {
output.flush();
} catch (IOException e) {
// eat
}
output.close();
}
}
}