/*
* 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>
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<
String>
c = null;
try {
PrivilegedExceptionAction<
Constructor<
String>>
runnable = new
PrivilegedExceptionAction<
Constructor<
String>>() {
@
Override
public
Constructor<
String>
run() throws
Exception {
Constructor<
String>
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.
*
* <p>The Encoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>The Encoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>The Decoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.</p>
*
* @return a new createDecoder instance
*/
public static
Decoder createDecoder() {
return new
Decoder(false);
}
/**
* Creates a state driven base64url decoder.
*
* <p>The Decoder instance is not thread-safe, and must not be shared between threads without establishing a
* happens-before relationship.</p>
*
* @return a new createDecoder instance
*/
public static
Decoder createURLDecoder() {
return new
Decoder(true);
}
/**
* Encodes a fixed and complete byte array into a Base64 String.
*
* <p>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.
*
* <p>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.
*
* <p>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.</p>
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.encodeString("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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.
*
* <p>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.</p>
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.encodeStringURL("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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.
*
* <p>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.</p>
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.ecncodeString("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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.
*
* <p>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.</p>
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.ecncodeStringURL("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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, false);
}
/**
* Encodes a fixed and complete byte buffer into a Base64 byte array.
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.ecncodeString("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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.
*
* <pre><code>
* // Encodes "ell"
* FlexBase64.ecncodeStringURL("hello".getBytes("US-ASCII"), 1, 4);
* </code></pre>
*
* @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.
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>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.</p>
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>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.</p>
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>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.
*
* <p>All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does not require
* BufferedOutputStream, which would lead to double buffering.</p>
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>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.</p>
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>All bytes written will be queued to an 8192 byte buffer. This stream, therefore, does
* not require BufferedOutputStream, which would lead to double buffering.</p>
*
* <p>This stream is not thread-safe, and should not be shared between threads, without establishing a
* happens-before relationship.</p>
*
* @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.
*
* <p>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.</p>
*
* <pre><code>
* 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));
* </code></pre>
*
* @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.
*
* <pre><code>
* 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));
* </code></pre>
*
* @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.
*
* <p>The decoder will skip white space, but will error if it detects corruption.</p>
*
* @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.
*
* <p>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.</p>
*
* <p>The decoder will skip white space, but will error if it detects corruption.</p>
*
* @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.
*
* <p>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</p>
*
* <p>The decoder will skip white space, but will error if it detects corruption.</p>
*
* @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.
*
* <p>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.</p>
*
* <p>The decoder will skip white space, but will error if it detects corruption.</p>
*
* <pre><code>
* 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));
* </code></pre>
*
*
* @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.
*
* <p>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.</p>
*/
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();
}
}
}