diff --git a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java index d647bf9df0..e75eb1e3e4 100644 --- a/core/src/main/java/org/jclouds/crypto/CryptoStreams.java +++ b/core/src/main/java/org/jclouds/crypto/CryptoStreams.java @@ -24,13 +24,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Mac; -import org.jclouds.encryption.internal.Base64; +import org.jclouds.encoding.internal.FlexBase64; import org.jclouds.io.InputSuppliers; import com.google.common.annotations.Beta; @@ -75,7 +76,7 @@ public class CryptoStreams { } public static String base64(byte[] in) { - return Base64.encodeBytes(in, Base64.DONT_BREAK_LINES); + return new String(FlexBase64.encodeBytes(in, 0, in.length, false), Charsets.US_ASCII); } /** @@ -91,33 +92,7 @@ public class CryptoStreams { */ @Beta public static String base64Url(byte[] in) { - String provisional = base64(in); - int length = provisional.length(); - if (length == 0) - return provisional; - // we know base64 is in 4 character chunks, so out of bounds risk here - else if (provisional.charAt(length - 2) == '=') - length-=2; - else if (provisional.charAt(length - 1) == '=') - length-=1; - - char[] tmp = new char[length]; - - for (int i = 0; i < length; i++) { - char c = provisional.charAt(i); - switch (c) { - case '+': - tmp[i] = '-'; - break; - case '/': - tmp[i] = '_'; - break; - default: - tmp[i] = c; - break; - } - } - return new String(tmp); + return FlexBase64.encodeURLString(in, 0, in.length); } /** @@ -130,7 +105,15 @@ public class CryptoStreams { * encoding */ public static byte[] base64(String in) { - return Base64.decode(in); + try { + ByteBuffer buffer = FlexBase64.decode(in); + byte [] returnVal = new byte [buffer.limit()]; + System.arraycopy(buffer.array(), buffer.arrayOffset(), returnVal, 0, buffer.limit()); + return returnVal; + } catch (IOException e) { + // unlikely as this is not reading from a stream + throw Throwables.propagate(e); + } } /** diff --git a/core/src/main/java/org/jclouds/encoding/internal/FlexBase64.java b/core/src/main/java/org/jclouds/encoding/internal/FlexBase64.java new file mode 100644 index 0000000000..5e3b59991b --- /dev/null +++ b/core/src/main/java/org/jclouds/encoding/internal/FlexBase64.java @@ -0,0 +1,1742 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.encoding.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +/** + * An efficient and flexible MIME Base64 implementation. + * + * @author Jason T. Greene + */ +public class FlexBase64 { + /* + * Note that this code heavily favors performance over reuse and clean style. + */ + + private static final byte[] ENCODING_TABLE; + private static final byte[] URL_ENCODING_TABLE; + private static final byte[] DECODING_TABLE = new byte[80]; + private static final Constructor STRING_CONSTRUCTOR; + + static { + try { + ENCODING_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes("ASCII"); + URL_ENCODING_TABLE = ENCODING_TABLE.clone(); + URL_ENCODING_TABLE[62] = '-'; + URL_ENCODING_TABLE[63] = '_'; + + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(); + } + + for (int i = 0; i < ENCODING_TABLE.length; i++) { + int v = (ENCODING_TABLE[i] & 0xFF) - 43; + DECODING_TABLE[v] = (byte)(i + 1); // zero = illegal + } + + // URL encoding + DECODING_TABLE['-' - 43] = 63; + DECODING_TABLE['_' - 43] = 64; + + 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); + } + + /** + * 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(); + } + + /** + * 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 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 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.

+ * + * @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 Base64 byte array. + * + *

+     *    // Encodes "ell"
+     *    FlexBase64.ecncodeString("hello".getBytes("US-ASCII"), 1, 4);
+     * 
+ * + * @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); + } + + /** + * Encodes a fixed and complete byte array into a Base64 String, following + * the RFC 4648 format. + * + *

+     *    // 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) + * @return a new String representing the Base64 output + */ + public static String encodeURLString(byte[] source, int pos, int limit) { + return Encoder.encodeString(source, pos, limit, false, true); + } + + /** + * Encodes a fixed and complete byte buffer into a Base64 URL String, following + * the RFC 4648 format. + * + * @param source the byte buffer to encode from + * @return a new String representing the Base64 output + */ + public static String encodeURLString(ByteBuffer source) { + return Encoder.encodeString(source, false, true); + } + + /** + * Decodes a Base64 encoded string into a new byte buffer. The returned byte buffer is a heap buffer, + * with an array offset of zero. It is therefore possible to retrieve the backing array using + * {@link java.nio.ByteBuffer#array()}, 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); + } + + /** + * Decodes a Base64 encoded byte buffer into a new byte buffer. The returned byte buffer is a heap buffer, + * with an array offset of zero. It is therefore possible to retrieve the backing array using + * {@link java.nio.ByteBuffer#array()}, 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); + } + + + /** + * Decodes a Base64 encoded byte array into a new byte buffer. The returned byte buffer is a heap buffer, + * with an array offset of zero. It is therefore possible to retrieve the backing array using + * {@link java.nio.ByteBuffer#array()}, 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); + } + + + /** + * 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); + } + + + /** + * 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 Encoder(boolean wrap) { + this.wrap = wrap; + } + + /** + * 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 = FlexBase64.ENCODING_TABLE; + + 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 = FlexBase64.ENCODING_TABLE; + + + 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); + olimit -= url && remainder > 0 ? 3 - remainder : 0; + char[] target = new char[olimit]; + int opos = 0; + int last = 0; + int count = 0; + int state = 0; + final byte[] ENCODING_TABLE = !url ? FlexBase64.ENCODING_TABLE : FlexBase64.URL_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) { + } + + return new String(target); + } + + private static byte[] encodeBytes(byte[] source, int pos, int limit, boolean wrap) { + 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 = FlexBase64.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); + + 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); + olimit -= url && remainder > 0 ? 3 - remainder : 0; + char[] target = new char[olimit]; + int opos = 0; + int last = 0; + int state = 0; + int count = 0; + final byte[] ENCODING_TABLE = !url ? FlexBase64.ENCODING_TABLE : FlexBase64.URL_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) { + } + + 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++] = ENCODING_TABLE[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) ENCODING_TABLE[last]; + for (int i = state; i < 3 && !url; 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) { + if (state > 0) { + target[pos++] = 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(ENCODING_TABLE[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 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() { + } + + + private static int nextByte(ByteBuffer buffer, int state, int last, boolean ignoreErrors) throws IOException { + return nextByte(buffer.get() & 0xFF, state, last, ignoreErrors); + } + + private static 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 static 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 = (DECODING_TABLE[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 static 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 static 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--; + } + + + } + + } + + + 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) 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 = createDecoder().decode(source, 0, source.length(), buffer, 0, size); + return ByteBuffer.wrap(buffer, 0, actual); + } + + private static ByteBuffer decode(byte[] source, int off, int limit) 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 = createDecoder().decode(source, off, limit, buffer, 0, size); + return ByteBuffer.wrap(buffer, 0, actual); + } + + private static ByteBuffer decode(ByteBuffer source) 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); + createDecoder().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); + } + + private EncoderInputStream(InputStream input, int bufferSize, boolean wrap) { + this.input = input; + buffer = new byte[bufferSize]; + this.encoder = new Encoder(wrap); + } + + 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; + for (int i = 0; i < len; i++) { + b[off + i] = overflow[pos + i]; + } + 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(); + } + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/encryption/internal/Base64.java b/core/src/main/java/org/jclouds/encryption/internal/Base64.java deleted file mode 100644 index 6f5866620c..0000000000 --- a/core/src/main/java/org/jclouds/encryption/internal/Base64.java +++ /dev/null @@ -1,1497 +0,0 @@ -/** - * Licensed to jclouds, Inc. (jclouds) under one or more - * contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. jclouds licenses this file - * to you 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. - */ -// -// NOTE: The following source code is the iHarder.net public domain -// Base64 library and is provided here as a convenience. For updates, -// problems, questions, etc. regarding this code, please visit: -// http://iharder.sourceforge.net/current/java/base64/ -// - -package org.jclouds.encryption.internal; - - -/** - * Encodes and decodes to and from Base64 notation. - * - *

- * Change Log: - *

- *
    - *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added - * some convenience methods for reading and writing to and from files.
  • - *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems - * with other encodings (like EBCDIC).
  • - *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the - * encoded data was a single byte.
  • - *
  • v2.0 - I got rid of methods that used booleans to set options. - * Now everything is more consolidated and cleaner. The code now detects - * when data that's being decoded is gzip-compressed and will decompress it - * automatically. Generally things are cleaner. You'll probably have to - * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
  • - *
  • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
  • - *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. - * This helps when using GZIP streams. - * Added the ability to GZip-compress objects before encoding them.
  • - *
  • v1.4 - Added helper methods to read/write files.
  • - *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • - *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
  • - *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • - *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • - *
- * - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.1 - */ -public class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ - public static final int NO_OPTIONS = 0; - - /** Specify encoding. */ - public static final int ENCODE = 1; - - - /** Specify decoding. */ - public static final int DECODE = 0; - - - /** Specify that data should be gzip-compressed. */ - public static final int GZIP = 2; - - - /** Don't break lines when encoding (violates strict Base64 specification) */ - public static final int DONT_BREAK_LINES = 8; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** Maximum line length (76) of Base64 output. */ - private static final int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private static final byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private static final byte NEW_LINE = (byte)'\n'; - - - /** Preferred encoding. */ - private static final String PREFERRED_ENCODING = "UTF-8"; - - - /** The 64 valid Base64 values. */ - private static final byte[] ALPHABET; - private static final byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ - { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - - /** Determine which ALPHABET to use. */ - static - { - byte[] __bytes; - try - { - __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException use) - { - __bytes = _NATIVE_ALPHABET; // Fall back to native encoding - } // end catch - ALPHABET = __bytes; - } // end static - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - * - *

Note

- * {@code +} and {@code -} both decode to 62. {@code /} and {@code _} both decode to 63 - * - * This accomodates URL-safe encoding - * @see url-safe base64 - **/ - private static final byte[] DECODABET = - { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Hyphen at decimal 45 - -9, // Decimal 45 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9 // Decimal 123 - 126 - /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // I think I end up not using the BAD_ENCODING indicator. - //private static final byte BAD_ENCODING = -9; // Indicates error in encoding - private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private static final byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - - /** Defeats instantiation. */ - private Base64(){} - - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes ) - { - encode3to4( threeBytes, 0, numSigBytes, b4, 0 ); - return b4; - } // end encode3to4 - - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset ) - { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); - - switch( numSigBytes ) - { - case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; - return destination; - - case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @since 1.4 - */ - public static String encodeObject( java.io.Serializable serializableObject ) - { - return encodeObject( serializableObject, NO_OPTIONS ); - } // end encodeObject - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. If the object - * cannot be serialized or there is another error, - * the method will return null. - *

- * Valid options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DONT_BREAK_LINES: don't break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeObject( myObj, Base64.GZIP ) or - *

- * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - * @since 2.0 - */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - { - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.io.ObjectOutputStream oos = null; - java.util.zip.GZIPOutputStream gzos = null; - - // Isolate options - int gzip = (options & GZIP); - int dontBreakLines = (options & DONT_BREAK_LINES); - - try - { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); - - // GZip? - if( gzip == GZIP ) - { - gzos = new java.util.zip.GZIPOutputStream( b64os ); - oos = new java.io.ObjectOutputStream( gzos ); - } // end if: gzip - else - oos = new java.io.ObjectOutputStream( b64os ); - - oos.writeObject( serializableObject ); - } // end try - catch( java.io.IOException e ) - { - e.printStackTrace(); - return null; - } // end catch - finally - { - try{ oos.close(); } catch( Exception e ){} - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try - { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( baos.toByteArray() ); - } // end catch - - } // end encode - - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @since 1.4 - */ - public static String encodeBytes( byte[] source ) - { - return encodeBytes( source, 0, source.length, NO_OPTIONS ); - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Valid options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DONT_BREAK_LINES: don't break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * - * @param source The data to convert - * @param options Specified options - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int options ) - { - return encodeBytes( source, 0, source.length, options ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len ) - { - return encodeBytes( source, off, len, NO_OPTIONS ); - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Valid options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DONT_BREAK_LINES: don't break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) - * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @see Base64#GZIP - * @see Base64#DONT_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int off, int len, int options ) - { - // Isolate options - int dontBreakLines = ( options & DONT_BREAK_LINES ); - int gzip = ( options & GZIP ); - - // Compress? - if( gzip == GZIP ) - { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - - try - { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - - gzos.write( source, off, len ); - gzos.close(); - } // end try - catch( java.io.IOException e ) - { - e.printStackTrace(); - return null; - } // end catch - finally - { - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try - { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( baos.toByteArray() ); - } // end catch - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else - { - // Convert option to boolean in way that code likes it. - boolean breakLines = dontBreakLines == 0; - - int len43 = len * 4 / 3; - byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) - { - encode3to4( source, d+off, 3, outBuff, e ); - - lineLength += 4; - if( breakLines && lineLength == MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if( d < len ) - { - encode3to4( source, d+off, len - d, outBuff, e ); - e += 4; - } // end if: some padding needed - - - // Return value according to relevant encoding. - try - { - return new String( outBuff, 0, e, PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String( outBuff, 0, e ); - } // end catch - - } // end else: don't compress - - } // end encodeBytes - - - - - -/* ******** D E C O D I N G ApiMetadata E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset ) - { - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else - { - try{ - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - }catch( Exception e){ - System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); - System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); - System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); - System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); - return -1; - } //e nd catch - } - } // end decodeToBytes - - - - - /** - * Very low-level access to decoding ASCII characters in - * the form of a byte array. Does not support automatically - * gunzipping or any other "fancy" features. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @return decoded data - * @since 1.3 - */ - public static byte[] decode( byte[] source, int off, int len ) - { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for( i = off; i < off+len; i++ ) - { - sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits - sbiDecode = DECODABET[ sbiCrop ]; - - if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better - { - if( sbiDecode >= EQUALS_SIGN_ENC ) - { - b4[ b4Posn++ ] = sbiCrop; - if( b4Posn > 3 ) - { - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( sbiCrop == EQUALS_SIGN ) - break; - } // end if: quartet built - - } // end if: equals sign or better - - } // end if: white space, equals sign or better - else - { - System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); - return null; - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode - - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode( String s ) - { - int modulo = s.length() % 4; - switch (modulo) { - case 2: - s += "=="; - break; - case 3: - s += "="; - break; - } - - byte[] bytes; - try - { - bytes = s.getBytes( PREFERRED_ENCODING ); - } // end try - catch( java.io.UnsupportedEncodingException uee ) - { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode( bytes, 0, bytes.length ); - - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - if( bytes != null && bytes.length >= 4 ) - { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) - { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try - { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); - - while( ( length = gzis.read( buffer ) ) >= 0 ) - { - baos.write(buffer,0,length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch( java.io.IOException e ) - { - // Just return originally-decoded bytes - } // end catch - finally - { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @since 1.5 - */ - public static Object decodeToObject( String encodedObject ) - { - // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try - { - bais = new java.io.ByteArrayInputStream( objBytes ); - ois = new java.io.ObjectInputStream( bais ); - - obj = ois.readObject(); - } // end try - catch( java.io.IOException e ) - { - e.printStackTrace(); - obj = null; - } // end catch - catch( java.lang.ClassNotFoundException e ) - { - e.printStackTrace(); - obj = null; - } // end catch - finally - { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} - } // end finally - - return obj; - } // end decodeObject - - - - /** - * Convenience method for encoding data to a file. - * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @return true if successful, false otherwise - * - * @since 2.1 - */ - public static boolean encodeToFile( byte[] dataToEncode, String filename ) - { - boolean success = false; - Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.ENCODE ); - bos.write( dataToEncode ); - success = true; - } // end try - catch( java.io.IOException e ) - { - - success = false; - } // end catch: IOException - finally - { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - return success; - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @return true if successful, false otherwise - * - * @since 2.1 - */ - public static boolean decodeToFile( String dataToDecode, String filename ) - { - boolean success = false; - Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.DECODE ); - bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); - success = true; - } // end try - catch( java.io.IOException e ) - { - success = false; - } // end catch: IOException - finally - { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - return success; - } // end decodeToFile - - - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - * - * @param filename Filename for reading encoded data - * @return decoded byte array or null if unsuccessful - * - * @since 2.1 - */ - public static byte[] decodeFromFile( String filename ) - { - byte[] decodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if( file.length() > Integer.MAX_VALUE ) - { - System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); - return null; - } // end if: file too big for int index - buffer = new byte[ (int)file.length() ]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.DECODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) - length += numBytes; - - // Save in a variable to return - decodedData = new byte[ length ]; - System.arraycopy( buffer, 0, decodedData, 0, length ); - - } // end try - catch( java.io.IOException e ) - { - System.err.println( "Error decoding from file " + filename ); - } // end catch: IOException - finally - { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return decodedData; - } // end decodeFromFile - - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - * - * @param filename Filename for reading binary data - * @return base64-encoded string or null if unsuccessful - * - * @since 2.1 - */ - public static String encodeFromFile( String filename ) - { - String encodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = new byte[ (int)(file.length() * 1.4) ]; - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.ENCODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) - length += numBytes; - - // Save in a variable to return - encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - - } // end try - catch( java.io.IOException e ) - { - System.err.println( "Error encoding from file " + filename ); - } // end catch: IOException - finally - { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return encodedData; - } // end encodeFromFile - - - - - /* ******** I N N E R C L A S S I N P U T S T R E A ApiMetadata ******** */ - - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream - { - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream( java.io.InputStream in ) - { - this( in, DECODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DONT_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         *     Note: Technically, this makes your encoding non-compliant.
-         * 
- *

- * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DONT_BREAK_LINES - * @since 2.0 - */ - public InputStream( java.io.InputStream in, int options ) - { - super( in ); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[ bufferLength ]; - this.position = -1; - this.lineLength = 0; - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - public int read() throws java.io.IOException - { - // Do we need to get data? - if( position < 0 ) - { - if( encode ) - { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for( int i = 0; i < 3; i++ ) - { - try - { - int b = in.read(); - - // If end of stream, b is -1. - if( b >= 0 ) - { - b3[i] = (byte)b; - numBinaryBytes++; - } // end if: not end of stream - - } // end try: read - catch( java.io.IOException e ) - { - // Only a problem if we got no data at all. - if( i == 0 ) - throw e; - - } // end catch - } // end for: each needed input byte - - if( numBinaryBytes > 0 ) - { - encode3to4( b3, 0, numBinaryBytes, buffer, 0 ); - position = 0; - numSigBytes = 4; - } // end if: got data - else - { - return -1; - } // end else - } // end if: encoding - - // Else decoding - else - { - byte[] b4 = new byte[4]; - int i = 0; - for( i = 0; i < 4; i++ ) - { - // Read four "meaningful" bytes: - int b = 0; - do{ b = in.read(); } - while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC ); - - if( b < 0 ) - break; // Reads a -1 if end of stream - - b4[i] = (byte)b; - } // end for: each needed input byte - - if( i == 4 ) - { - numSigBytes = decode4to3( b4, 0, buffer, 0 ); - position = 0; - } // end if: got four characters - else if( i == 0 ){ - return -1; - } // end else if: also padded correctly - else - { - // Must have broken out from above. - throw new java.io.IOException( "Improperly padded Base64 input." ); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if( position >= 0 ) - { - // End of relevant data? - if( /*!encode &&*/ position >= numSigBytes ) - return -1; - - if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) - { - lineLength = 0; - return '\n'; - } // end if - else - { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[ position++ ]; - - if( position >= bufferLength ) - position = -1; - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else - { - // When JDK1.4 is more accepted, use an assertion here. - throw new java.io.IOException( "Error in Base64 code reading stream." ); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - public int read( byte[] dest, int off, int len ) throws java.io.IOException - { - int i; - int b; - for( i = 0; i < len; i++ ) - { - b = read(); - - //if( b < 0 && i == 0 ) - // return -1; - - if( b >= 0 ) - dest[off + i] = (byte)b; - else if( i == 0 ) - return -1; - else - break; // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - - - - - /* ******** I N N E R C L A S S O U T P U T S T R E A ApiMetadata ******** */ - - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class OutputStream extends java.io.FilterOutputStream - { - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out ) - { - this( out, ENCODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DONT_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         *     Note: Technically, this makes your encoding non-compliant.
-         * 
- *

- * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DONT_BREAK_LINES - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out, int options ) - { - super( out ); - this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; - this.encode = (options & ENCODE) == ENCODE; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[ bufferLength ]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - public void write(int theByte) throws java.io.IOException - { - // Encoding suspended? - if( suspendEncoding ) - { - super.out.write( theByte ); - return; - } // end if: supsended - - // Encode? - if( encode ) - { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) // Enough to encode. - { - out.write( encode3to4( b4, buffer, bufferLength ) ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) - { - out.write( NEW_LINE ); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else - { - // Meaningful Base64 character? - if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC ) - { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) // Enough to output. - { - int len = Base64.decode4to3( buffer, 0, b4, 0 ); - out.write( b4, 0, len ); - //out.write( Base64.decode4to3( buffer ) ); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC ) - { - throw new java.io.IOException( "Invalid character in Base64 data." ); - } // end else: not white space either - } // end else: decoding - } // end write - - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - public void write( byte[] theBytes, int off, int len ) throws java.io.IOException - { - // Encoding suspended? - if( suspendEncoding ) - { - super.out.write( theBytes, off, len ); - return; - } // end if: supsended - - for( int i = 0; i < len; i++ ) - { - write( theBytes[ off + i ] ); - } // end for: each byte written - - } // end write - - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - */ - public void flushBase64() throws java.io.IOException - { - if( position > 0 ) - { - if( encode ) - { - out.write( encode3to4( b4, buffer, position ) ); - position = 0; - } // end if: encoding - else - { - throw new java.io.IOException( "Base64 input not properly padded." ); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - public void close() throws java.io.IOException - { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base640-encoded data in a stream. - * - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException - { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base640-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() - { - this.suspendEncoding = false; - } // end resumeEncoding - - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/core/src/main/java/org/jclouds/io/InputSuppliers.java b/core/src/main/java/org/jclouds/io/InputSuppliers.java index 51cb0d4d2b..2171aed9c1 100644 --- a/core/src/main/java/org/jclouds/io/InputSuppliers.java +++ b/core/src/main/java/org/jclouds/io/InputSuppliers.java @@ -23,10 +23,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.io.IOException; import java.io.InputStream; -import org.jclouds.encryption.internal.Base64; +import org.jclouds.encoding.internal.FlexBase64; import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.io.ByteStreams; import com.google.common.io.InputSupplier; @@ -41,35 +40,27 @@ public class InputSuppliers { /** * base64 encodes bytes from the supplied supplier as they are read. */ - public static Base64InputSupplier base64Encoder(InputSupplier supplier) throws IOException { - return new Base64InputSupplier(supplier, Base64.ENCODE + Base64.DONT_BREAK_LINES); + public static InputSupplier base64Encoder(final InputSupplier supplier) throws IOException { + return new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return FlexBase64.createEncoderInputStream(supplier.getInput(), 8192, false); + } + }; } /** * base64 decodes bytes from the supplied supplier as they are read. */ - public static Base64InputSupplier base64Decoder(InputSupplier supplier) throws IOException { - return new Base64InputSupplier(supplier, Base64.DECODE); + public static InputSupplier base64Decoder(final InputSupplier supplier) throws IOException { + return new InputSupplier() { + @Override + public InputStream getInput() throws IOException { + return FlexBase64.createDecoderInputStream(supplier.getInput()); + } + }; } - - @VisibleForTesting - static class Base64InputSupplier implements InputSupplier { - - private final InputSupplier delegate; - private final int mode; - - Base64InputSupplier(InputSupplier inputSupplier, int mode) { - this.delegate = checkNotNull(inputSupplier, "delegate"); - this.mode = mode; - } - - @Override - public InputStream getInput() throws IOException { - return new Base64.InputStream(delegate.getInput(), mode); - } - - } - + public static InputSupplier of(final InputStream in) { checkNotNull(in, "in"); return new InputSupplier() { diff --git a/core/src/test/java/org/jclouds/crypto/CryptoStreamsTest.java b/core/src/test/java/org/jclouds/crypto/CryptoStreamsTest.java index 3255a64d50..d55e695895 100644 --- a/core/src/test/java/org/jclouds/crypto/CryptoStreamsTest.java +++ b/core/src/test/java/org/jclouds/crypto/CryptoStreamsTest.java @@ -92,7 +92,9 @@ public class CryptoStreamsTest { @Test public void testBase64DecodeWithoutSinglePad() { - assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhc3U="), Charsets.UTF_8), "any carnal pleasu"); + String expect = "any carnal pleasu"; + String compare = new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhc3U="), Charsets.UTF_8); + assertEquals(compare,expect); assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhc3U"), Charsets.UTF_8), "any carnal pleasu"); } diff --git a/core/src/test/java/org/jclouds/encoding/internal/FlexBase64Test.java b/core/src/test/java/org/jclouds/encoding/internal/FlexBase64Test.java new file mode 100644 index 0000000000..7e43f5393d --- /dev/null +++ b/core/src/test/java/org/jclouds/encoding/internal/FlexBase64Test.java @@ -0,0 +1,514 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you 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 org.jclouds.encoding.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.primitives.Bytes; + + +/** + * @author Jason T. Greene + */ +public class FlexBase64Test { + + public static final String TOWEL = "A towel, it says, is about the most massively useful thing an interstellar " + + "hitchhiker can have. Partly it has great practical value - you can wrap it around you for warmth as you " + + "bound across the cold moons of Jaglan Beta; you can lie on it on the brilliant marble-sanded beaches of " + + "Santraginus V, inhaling the heady sea vapours; you can sleep under it beneath the stars which shine so " + + "redly on the desert world of Kakrafoon; use it to sail a mini raft down the slow heavy river Moth; wet " + + "it for use in hand-to- hand-combat; wrap it round your head to ward off noxious fumes or to avoid the " + + "gaze of the Ravenous Bugblatter Beast of Traal (a mindboggingly stupid animal, it assumes that if you " + + "can't see it, it can't see you - daft as a bush, but very ravenous); you can wave your towel in " + + "emergencies as a distress signal, and of course dry yourself off with it if it still seems to be clean " + + "enough." + + "\n\n" + + "More importantly, a towel has immense psychological value. For some reason, if a strag " + + "(strag: non-hitch hiker) discovers that a hitch hiker has his towel with him, he will automatically " + + "assume that he is also in possession of a toothbrush, face flannel, soap, tin of biscuits, flask, compass, " + + "map, ball of string, gnat spray, wet weather gear, space suit etc., etc. Furthermore, the strag will then " + + "happily lend the hitch hiker any of these or a dozen other items that the hitch hiker might accidentally " + + "have \"lost\". What the strag will think is that any man who can hitch the length and breadth of the " + + "galaxy, rough it, slum it, struggle against terrible odds, win through, and still knows where his towel " + + "is is clearly a man to be reckoned with.\n"; + + public static final String TOWEL_BASE64 = + "QSB0b3dlbCwgaXQgc2F5cywgaXMgYWJvdXQgdGhlIG1vc3QgbWFzc2l2ZWx5IHVzZWZ1bCB0aGlu\r\n"+ + "ZyBhbiBpbnRlcnN0ZWxsYXIgaGl0Y2hoaWtlciBjYW4gaGF2ZS4gUGFydGx5IGl0IGhhcyBncmVh\r\n"+ + "dCBwcmFjdGljYWwgdmFsdWUgLSB5b3UgY2FuIHdyYXAgaXQgYXJvdW5kIHlvdSBmb3Igd2FybXRo\r\n"+ + "IGFzIHlvdSBib3VuZCBhY3Jvc3MgdGhlIGNvbGQgbW9vbnMgb2YgSmFnbGFuIEJldGE7IHlvdSBj\r\n"+ + "YW4gbGllIG9uIGl0IG9uIHRoZSBicmlsbGlhbnQgbWFyYmxlLXNhbmRlZCBiZWFjaGVzIG9mIFNh\r\n"+ + "bnRyYWdpbnVzIFYsIGluaGFsaW5nIHRoZSBoZWFkeSBzZWEgdmFwb3VyczsgeW91IGNhbiBzbGVl\r\n"+ + "cCB1bmRlciBpdCBiZW5lYXRoIHRoZSBzdGFycyB3aGljaCBzaGluZSBzbyByZWRseSBvbiB0aGUg\r\n"+ + "ZGVzZXJ0IHdvcmxkIG9mIEtha3JhZm9vbjsgdXNlIGl0IHRvIHNhaWwgYSBtaW5pIHJhZnQgZG93\r\n"+ + "biB0aGUgc2xvdyBoZWF2eSByaXZlciBNb3RoOyB3ZXQgaXQgZm9yIHVzZSBpbiBoYW5kLXRvLSBo\r\n"+ + "YW5kLWNvbWJhdDsgd3JhcCBpdCByb3VuZCB5b3VyIGhlYWQgdG8gd2FyZCBvZmYgbm94aW91cyBm\r\n"+ + "dW1lcyBvciB0byBhdm9pZCB0aGUgZ2F6ZSBvZiB0aGUgUmF2ZW5vdXMgQnVnYmxhdHRlciBCZWFz\r\n"+ + "dCBvZiBUcmFhbCAoYSBtaW5kYm9nZ2luZ2x5IHN0dXBpZCBhbmltYWwsIGl0IGFzc3VtZXMgdGhh\r\n"+ + "dCBpZiB5b3UgY2FuJ3Qgc2VlIGl0LCBpdCBjYW4ndCBzZWUgeW91IC0gZGFmdCBhcyBhIGJ1c2gs\r\n"+ + "IGJ1dCB2ZXJ5IHJhdmVub3VzKTsgeW91IGNhbiB3YXZlIHlvdXIgdG93ZWwgaW4gZW1lcmdlbmNp\r\n"+ + "ZXMgYXMgYSBkaXN0cmVzcyBzaWduYWwsIGFuZCBvZiBjb3Vyc2UgZHJ5IHlvdXJzZWxmIG9mZiB3\r\n"+ + "aXRoIGl0IGlmIGl0IHN0aWxsIHNlZW1zIHRvIGJlIGNsZWFuIGVub3VnaC4KCk1vcmUgaW1wb3J0\r\n"+ + "YW50bHksIGEgdG93ZWwgaGFzIGltbWVuc2UgcHN5Y2hvbG9naWNhbCB2YWx1ZS4gRm9yIHNvbWUg\r\n"+ + "cmVhc29uLCBpZiBhIHN0cmFnIChzdHJhZzogbm9uLWhpdGNoIGhpa2VyKSBkaXNjb3ZlcnMgdGhh\r\n"+ + "dCBhIGhpdGNoIGhpa2VyIGhhcyBoaXMgdG93ZWwgd2l0aCBoaW0sIGhlIHdpbGwgYXV0b21hdGlj\r\n"+ + "YWxseSBhc3N1bWUgdGhhdCBoZSBpcyBhbHNvIGluIHBvc3Nlc3Npb24gb2YgYSB0b290aGJydXNo\r\n"+ + "LCBmYWNlIGZsYW5uZWwsIHNvYXAsIHRpbiBvZiBiaXNjdWl0cywgZmxhc2ssIGNvbXBhc3MsIG1h\r\n"+ + "cCwgYmFsbCBvZiBzdHJpbmcsIGduYXQgc3ByYXksIHdldCB3ZWF0aGVyIGdlYXIsIHNwYWNlIHN1\r\n"+ + "aXQgZXRjLiwgZXRjLiBGdXJ0aGVybW9yZSwgdGhlIHN0cmFnIHdpbGwgdGhlbiBoYXBwaWx5IGxl\r\n"+ + "bmQgdGhlIGhpdGNoIGhpa2VyIGFueSBvZiB0aGVzZSBvciBhIGRvemVuIG90aGVyIGl0ZW1zIHRo\r\n"+ + "YXQgdGhlIGhpdGNoIGhpa2VyIG1pZ2h0IGFjY2lkZW50YWxseSBoYXZlICJsb3N0Ii4gV2hhdCB0\r\n"+ + "aGUgc3RyYWcgd2lsbCB0aGluayBpcyB0aGF0IGFueSBtYW4gd2hvIGNhbiBoaXRjaCB0aGUgbGVu\r\n"+ + "Z3RoIGFuZCBicmVhZHRoIG9mIHRoZSBnYWxheHksIHJvdWdoIGl0LCBzbHVtIGl0LCBzdHJ1Z2ds\r\n"+ + "ZSBhZ2FpbnN0IHRlcnJpYmxlIG9kZHMsIHdpbiB0aHJvdWdoLCBhbmQgc3RpbGwga25vd3Mgd2hl\r\n"+ + "cmUgaGlzIHRvd2VsIGlzIGlzIGNsZWFybHkgYSBtYW4gdG8gYmUgcmVja29uZWQgd2l0aC4K\r\n"; + + private static final String KNOWLEDGE = + "Man is distinguished, not only by his reason, but by this singular passion from " + + "other animals, which is a lust of the mind, that by a perseverance of delight " + + "in the continued and indefatigable generation of knowledge, exceeds the short " + + "vehemence of any carnal pleasure."; + + private static final String KNOWLEDGE_ENCODED = + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\r\n" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\r\n" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=\r\n"; + @Test + public void testEncoderDecoder() throws IOException { + byte[] nums = new byte[32768]; + for (int i = 0; i < 32768; i++) { + nums[i] = (byte) (i % 255); + } + + byte[] output = new byte[65535]; + FlexBase64.Encoder encoder = FlexBase64.createEncoder(true); + int last = encoder.encode(nums, 0, nums.length, output, 0, output.length); + last = encoder.complete(output, last); + + byte[] decode = new byte[nums.length]; + FlexBase64.Decoder decoder = FlexBase64.createDecoder(); + last = decoder.decode(output, 0, last, decode, 0, decode.length); + + Assert.assertEquals(nums.length, last); + + for (int i = 0; i < last; i++) { + Assert.assertEquals(nums[i], decode[i]); + } + } + + @Test + public void testEncoderDecoderBuffer() throws IOException { + byte[] nums = new byte[32768]; + for (int i = 0; i < 32768; i++) { + nums[i] = (byte) (i % 255); + } + + ByteBuffer source = ByteBuffer.wrap(nums); + ByteBuffer target = ByteBuffer.allocate(65535); + + FlexBase64.Encoder encoder = FlexBase64.createEncoder(true); + encoder.encode(source, target); + encoder.complete(target); + + ByteBuffer decoded = ByteBuffer.allocate(nums.length); + FlexBase64.Decoder decoder = FlexBase64.createDecoder(); + target.flip(); + decoder.decode(target, decoded); + + decoded.flip(); + + Assert.assertEquals(nums.length, decoded.remaining()); + + for (int i = 0; i < nums.length; i++) { + Assert.assertEquals(nums[i], decoded.get()); + } + } + + @Test + public void testDrain() throws IOException { + byte[] bytes = "c3VyZS4=\r\n\r\n!".getBytes("US-ASCII"); + ByteBuffer source = ByteBuffer.wrap(bytes); + ByteBuffer target = ByteBuffer.allocateDirect(100); + FlexBase64.createDecoder().decode(source, target); + Assert.assertEquals((byte) '\r' & 0xFF, source.get() & 0xFF); + Assert.assertEquals((byte) '\n' & 0xFF, source.get() & 0xFF); + Assert.assertEquals((byte) '!' & 0xFF, source.get() & 0xFF); + + byte[] dest = new byte[100]; + FlexBase64.Decoder decoder = FlexBase64.createDecoder(); + decoder.decode(bytes, 0, bytes.length, dest, 0, dest.length); + Assert.assertEquals(10, decoder.getLastInputPosition()); + + bytes = "YXN1cmUu\r\n\r\nA".getBytes("US-ASCII"); + dest = new byte[6]; + decoder = FlexBase64.createDecoder(); + decoder.decode(bytes, 0, bytes.length, dest, 0, dest.length); + Assert.assertEquals(12, decoder.getLastInputPosition()); + } + + @Test + public void testEncoderDecoderBufferLoops() throws IOException { + byte[] nums = new byte[32768]; + for (int i = 0; i < 32768; i++) { + nums[i] = (byte) (i % 255); + } + ByteBuffer source = ByteBuffer.wrap(nums); + ByteBuffer target = ByteBuffer.allocate(65535); + + FlexBase64.Encoder encoder = FlexBase64.createEncoder(true); + int limit = target.limit(); + target.limit(100); + while (source.remaining() > 0) { + encoder.encode(source, target); + int add = limit - target.position(); + add = add < 100 ? add : 100; + target.limit(target.limit() + add); + } + encoder.complete(target); + + ByteBuffer decoded = ByteBuffer.allocate(nums.length); + FlexBase64.Decoder decoder = FlexBase64.createDecoder(); + target.flip(); + + limit = decoded.limit(); + decoded.limit(100); + while (target.remaining() > 0) { + decoder.decode(target, decoded); + int add = limit - decoded.position(); + add = add < 100 ? add : 100; + decoded.limit(decoded.position() + add); + } + + decoded.flip(); + + Assert.assertEquals(nums.length, decoded.remaining()); + + for (int i = 0; i < nums.length; i++) { + Assert.assertEquals(nums[i], decoded.get()); + } + } + + @Test + public void testEncoderDecoderLoopWithOffset() throws IOException { + byte[] nums = new byte[32768]; + for (int i = 0; i < 32768; i++) { + nums[i] = (byte) (i % 255); + } + + byte[] output = new byte[65535]; + FlexBase64.Encoder encoder = FlexBase64.createEncoder(true); + + int opos = 5; + int pos = 0; + while (pos < 32768) { + opos = encoder.encode(nums, pos, nums.length, output, opos, opos + 10000); + pos = encoder.getLastInputPosition(); + } + opos = encoder.complete(output, opos); + + byte[] decode = new byte[nums.length]; + FlexBase64.Decoder decoder = FlexBase64.createDecoder(); + int stop = opos; + pos = 5; + int last = 0; + while (pos < stop) { + last = decoder.decode(output, pos, stop, decode, last, last + 10000); + pos = decoder.getLastInputPosition(); + } + + Assert.assertEquals(nums.length, last); + + for (int i = 0; i < last; i++) { + Assert.assertEquals(nums[i], decode[i]); + } + } + + @Test + public void testEncodeString() throws Exception { + byte[] data = ("Man is distinguished, not only by his reason, but by this singular passion from " + + "other animals, which is a lust of the mind, that by a perseverance of delight " + + "in the continued and indefatigable generation of knowledge, exceeds the short " + + "vehemence of any carnal pleasure.").getBytes("US-ASCII"); + + String expect = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\r\n" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\r\n" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=\r\n"; + + Assert.assertEquals(expect, FlexBase64.encodeString(data, true)); + Assert.assertEquals(expect, FlexBase64.encodeString(ByteBuffer.wrap(data), true)); + + byte[] data2 = new byte[data.length + 10]; + System.arraycopy(data, 0, data2, 5, data.length); + Assert.assertEquals(expect, FlexBase64.encodeString(data2, 5, data.length + 5, true)); + + } + + @Test + public void testEncodeBytes() throws Exception { + byte[] data = ("Man is distinguished, not only by his reason, but by this singular passion from " + + "other animals, which is a lust of the mind, that by a perseverance of delight " + + "in the continued and indefatigable generation of knowledge, exceeds the short " + + "vehemence of any carnal pleasure.").getBytes("US-ASCII"); + + byte[] expect = ("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\r\n" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\r\n" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=\r\n").getBytes("US-ASCII"); + + Assert.assertEquals(Bytes.asList(expect), Bytes.asList(FlexBase64.encodeBytes(data, 0, data.length, true))); + + } + + @Test + public void testDecodeString() throws Exception { + String expect = "Man is distinguished, not only by his reason, but by this singular passion from " + + "other animals, which is a lust of the mind, that by a perseverance of delight " + + "in the continued and indefatigable generation of knowledge, exceeds the short " + + "vehemence of any carnal pleasure."; + + String encoded = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\r\n" + + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\r\n" + + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\r\n" + + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\r\n" + + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=\r\n"; + + ByteBuffer buffer = FlexBase64.decode(encoded); + Assert.assertEquals(expect, new String(buffer.array(), buffer.arrayOffset(), buffer.limit(), "US-ASCII")); + + buffer = FlexBase64.decode(ByteBuffer.wrap(encoded.getBytes("US-ASCII"))); + Assert.assertEquals(expect, new String(buffer.array(), buffer.arrayOffset(), buffer.limit(), "US-ASCII")); + + buffer = FlexBase64.decode(encoded.getBytes("US-ASCII"), 0, encoded.length()); + Assert.assertEquals(expect, new String(buffer.array(), buffer.arrayOffset(), buffer.limit(), "US-ASCII")); + + byte[] output = new byte[expect.length()]; + FlexBase64.createDecoder().decode(encoded, output); + Assert.assertEquals(expect, new String(output, 0, output.length, "US-ASCII")); + } + + @Test + public void testURLString() throws Exception { + byte[] source = {0x6b, (byte) 0xf6, (byte) 0xfe}; + Assert.assertEquals("a_b-", FlexBase64.encodeURLString(source, 0, 3)); + Assert.assertEquals(Bytes.asList(source), Bytes.asList(FlexBase64.decode("a_b-").array())); + String actual = FlexBase64.encodeURLString("test".getBytes("UTF-8"), 0, 4); + Assert.assertEquals("dGVzdA", actual); + ByteBuffer decode = FlexBase64.decode(actual); + Assert.assertEquals("test", new String(decode.array(), 0, decode.limit(), "UTF-8")); + byte[] bytes = TOWEL.getBytes("UTF-8"); + Assert.assertEquals(TOWEL_BASE64.replace("\r\n",""), FlexBase64.encodeURLString(ByteBuffer.wrap(bytes))); + bytes = KNOWLEDGE.getBytes("UTF-8"); + String replace = KNOWLEDGE_ENCODED.replace("\r\n", ""); + Assert.assertEquals(replace.substring(0, replace.length() - 1), FlexBase64.encodeURLString(ByteBuffer.wrap(bytes))); + } + + @Test + public void testEncoderInputStream() throws Exception { + FlexBase64.EncoderInputStream encoderInputStream = FlexBase64.createEncoderInputStream(new ByteArrayInputStream( + TOWEL.getBytes("US-ASCII"))); + ByteBuffer base64 = ByteBuffer.wrap(TOWEL_BASE64.getBytes("US-ASCII")); + verifyStreamContents(encoderInputStream, base64); + + encoderInputStream = FlexBase64.createEncoderInputStream(new ByteArrayInputStream(TOWEL.getBytes("US-ASCII")), + 8192, false); + base64 = ByteBuffer.wrap(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")); + verifyStreamContents(encoderInputStream, base64); + + encoderInputStream = FlexBase64.createEncoderInputStream(new ByteArrayInputStream(TOWEL.getBytes("US-ASCII"))); + base64 = ByteBuffer.wrap(TOWEL_BASE64.getBytes("US-ASCII")); + verifyStreamContentsOneByte(encoderInputStream, base64); + + encoderInputStream = FlexBase64.createEncoderInputStream(new ByteArrayInputStream(TOWEL.getBytes("US-ASCII")), + 8192, false); + base64 = ByteBuffer.wrap(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")); + verifyStreamContentsOneByte(encoderInputStream, base64); + } + + @Test + public void testDecoderInputStream() throws Exception { + FlexBase64.DecoderInputStream stream = FlexBase64.createDecoderInputStream(new ByteArrayInputStream(TOWEL_BASE64 + .getBytes("US-ASCII"))); + ByteBuffer base64 = ByteBuffer.wrap(TOWEL.getBytes("US-ASCII")); + verifyStreamContents(stream, base64); + + stream = FlexBase64.createDecoderInputStream(new ByteArrayInputStream(TOWEL_BASE64.replace("\r\n", "").getBytes( + "US-ASCII"))); + base64 = ByteBuffer.wrap(TOWEL.getBytes("US-ASCII")); + verifyStreamContents(stream, base64); + + stream = FlexBase64.createDecoderInputStream(new ByteArrayInputStream(TOWEL_BASE64.getBytes("US-ASCII"))); + base64 = ByteBuffer.wrap(TOWEL.getBytes("US-ASCII")); + verifyStreamContentsOneByte(stream, base64); + + stream = FlexBase64.createDecoderInputStream(new ByteArrayInputStream(TOWEL_BASE64.replace("\r\n", "").getBytes( + "US-ASCII"))); + base64 = ByteBuffer.wrap(TOWEL.replace("\r\n", "").getBytes("US-ASCII")); + verifyStreamContentsOneByte(stream, base64); + + stream = FlexBase64.createDecoderInputStream( + new ByteArrayInputStream(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")), 10); + base64 = ByteBuffer.wrap(TOWEL.replace("\r\n", "").getBytes("US-ASCII")); + verifyStreamContentsOneByte(stream, base64); + } + + @Test + public void testEncoderOutputStream() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + FlexBase64.EncoderOutputStream stream = FlexBase64.createEncoderOutputStream(baos); + byte[] towel = TOWEL.getBytes("US-ASCII"); + stream.write(towel); + stream.close(); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createEncoderOutputStream(baos, 8192, false); + stream.write(towel); + stream.close(); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createEncoderOutputStream(baos, 8192, true); + chunkWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createEncoderOutputStream(baos, 8192, false); + chunkWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createEncoderOutputStream(baos, 8192, true); + oneByteWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createEncoderOutputStream(baos, 8192, false); + oneByteWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + } + + @Test + public void testDecoderOutputStream() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + FlexBase64.DecoderOutputStream stream = FlexBase64.createDecoderOutputStream(baos); + byte[] towel = TOWEL_BASE64.getBytes("US-ASCII"); + byte[] towelStrip = TOWEL_BASE64.replace("\r\n", "").getBytes("US-ASCII"); + + stream.write(towel); + stream.close(); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos); + stream.write(towelStrip); + stream.close(); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos); + chunkWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos); + chunkWrite(stream, towelStrip); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos); + oneByteWrite(stream, towel); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos); + oneByteWrite(stream, towelStrip); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + baos.reset(); + stream = FlexBase64.createDecoderOutputStream(baos, 10); + chunkWrite(stream, towelStrip); + Assert.assertEquals(Bytes.asList(TOWEL.getBytes("US-ASCII")), Bytes.asList(baos.toByteArray())); + + } + + private void chunkWrite(OutputStream stream, byte[] towel) throws IOException { + ByteBuffer wrap = ByteBuffer.wrap(towel); + int remaining = wrap.remaining(); + while (remaining > 0) { + int left = remaining < 100 ? remaining : 100; + byte[] chunk = new byte[left]; + wrap.get(chunk); + stream.write(chunk); + remaining = wrap.remaining(); + } + + stream.close(); + } + + private void oneByteWrite(OutputStream stream, byte[] towel) throws IOException { + ByteBuffer wrap = ByteBuffer.wrap(towel); + while (wrap.remaining() > 0) { + stream.write(wrap.get() & 0xFF); + } + + stream.close(); + } + + private void verifyStreamContentsOneByte(InputStream inputStream, ByteBuffer base64) throws IOException { + int read = inputStream.read(); + while (read > -1) { + byte expected = base64.get(); + Assert.assertEquals(expected, read); + + read = inputStream.read(); + } + + Assert.assertEquals(0, base64.remaining()); + } + + private void verifyStreamContents(InputStream inputStream, ByteBuffer base64) throws IOException { + byte[] buffer = new byte[100]; + int read = inputStream.read(buffer); + while (read > -1) { + for (int i = 0; i < read; i++) { + byte expected = base64.get(); + byte actual = buffer[i]; + Assert.assertEquals(expected, actual); + } + + read = inputStream.read(buffer); + } + + Assert.assertEquals(0, base64.remaining()); + } + +} \ No newline at end of file