diff --git a/spring-webflow/.classpath b/spring-webflow/.classpath index 19de29c7..c23acd06 100644 --- a/spring-webflow/.classpath +++ b/spring-webflow/.classpath @@ -5,7 +5,6 @@ - diff --git a/spring-webflow/changelog.txt b/spring-webflow/changelog.txt index 760f8bcd..0e0c0bdc 100644 --- a/spring-webflow/changelog.txt +++ b/spring-webflow/changelog.txt @@ -37,6 +37,7 @@ Package org.springframework.webflow.engine Package org.springframework.webflow.execution * Added FlowExecutionListener.sessionCreated(RequestContext, FlowSession) callback, useful for setting flow attributes before the flow starts (SWF-223). +* ClientContinuationFlowExecutionRepository now uses 'URL-safe Base64 encoding' (SWF-270). Package org.springframework.webflow.executor * Added resource URL encoding to redirects in a JSF environment (SWF-256). diff --git a/spring-webflow/docs/reference/src/overview.xml b/spring-webflow/docs/reference/src/overview.xml index c423b5ac..f8053cfe 100644 --- a/spring-webflow/docs/reference/src/overview.xml +++ b/spring-webflow/docs/reference/src/overview.xml @@ -333,8 +333,7 @@ Util, Flow Definition, Conversation, - Flow Execution, - repository.continuation requires commons-codec 1.0 if using client continuations + Flow Execution diff --git a/spring-webflow/ivy.xml b/spring-webflow/ivy.xml index 088b1e27..bfccffb5 100644 --- a/spring-webflow/ivy.xml +++ b/spring-webflow/ivy.xml @@ -27,7 +27,6 @@ --> - diff --git a/spring-webflow/pom.xml b/spring-webflow/pom.xml index 1cde5576..3c2a0479 100644 --- a/spring-webflow/pom.xml +++ b/spring-webflow/pom.xml @@ -1,160 +1,154 @@ - - - 4.0.0 - org.springframework - spring-webflow - jar - Spring Web Flow - 1.0.2-SNAPSHOT - Spring Web Flow - http://www.springframework.org - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects - scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects - 1.0.2 - http://svn.sourceforge.net/viewcvs.cgi/springframework/spring-projects - - - Spring Framework - http://www.springframework.org/ - - - - - commons-codec - commons-codec - 1.1 - true - - - commons-logging - commons-logging - 1.0.4 - - - concurrent - concurrent - 1.3.4 - true - - - javax.portlet - portlet-api - 1.0 - provided - - - javax.servlet - servlet-api - 2.4 - provided - - - junit - junit - 3.8.1 - true - - - ognl - ognl - 2.6.9 - - - org.apache.myfaces.core - myfaces-api - 1.1.4 - provided - - - org.apache.myfaces.core - myfaces-impl - 1.1.4 - true - - - struts - struts - 1.2.9 - true - - - - org.springframework - spring-beans - 2.0.2 - - - org.springframework - spring-binding - ${project.version} - - - org.springframework - spring-context - 2.0.2 - - - org.springframework - spring-core - 2.0.2 - - - org.springframework - spring-portlet - 2.0.2 - true - - - org.springframework - spring-struts - 2.0.2 - true - - - org.springframework - spring-web - 2.0.2 - - - org.springframework - spring-webmvc - 2.0.2 - true - - - - log4j - log4j - 1.2.13 - runtime - true - - - - com.cenqua.clover - clover - 1.3.12 - test - - - easymock - easymock - 2.2 - test - - - org.springframework - spring-mock - 2.0.2 - test - - + + + 4.0.0 + org.springframework + spring-webflow + jar + Spring Web Flow + 1.0.2-SNAPSHOT + Spring Web Flow + http://www.springframework.org + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects + scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects + 1.0.2 + http://svn.sourceforge.net/viewcvs.cgi/springframework/spring-projects + + + Spring Framework + http://www.springframework.org/ + + + + + commons-logging + commons-logging + 1.0.4 + + + concurrent + concurrent + 1.3.4 + true + + + javax.portlet + portlet-api + 1.0 + provided + + + javax.servlet + servlet-api + 2.4 + provided + + + junit + junit + 3.8.1 + true + + + ognl + ognl + 2.6.9 + + + org.apache.myfaces.core + myfaces-api + 1.1.4 + provided + + + org.apache.myfaces.core + myfaces-impl + 1.1.4 + true + + + struts + struts + 1.2.9 + true + + + + org.springframework + spring-beans + 2.0.2 + + + org.springframework + spring-binding + ${project.version} + + + org.springframework + spring-context + 2.0.2 + + + org.springframework + spring-core + 2.0.2 + + + org.springframework + spring-portlet + 2.0.2 + true + + + org.springframework + spring-struts + 2.0.2 + true + + + org.springframework + spring-web + 2.0.2 + + + org.springframework + spring-webmvc + 2.0.2 + true + + + + log4j + log4j + 1.2.13 + runtime + true + + + + com.cenqua.clover + clover + 1.3.12 + test + + + easymock + easymock + 2.2 + test + + + org.springframework + spring-mock + 2.0.2 + test + + \ No newline at end of file diff --git a/spring-webflow/readme.txt b/spring-webflow/readme.txt index a43370d7..5c6c12e4 100644 --- a/spring-webflow/readme.txt +++ b/spring-webflow/readme.txt @@ -52,8 +52,8 @@ optional, i.e. just necessary for certain functionality. * spring-webflow-1.0.jar - Contents: The Spring Web Flow system - Dependencies: Commons Logging, spring-beans, spring-core, spring-context, spring-web, spring-binding, OGNL - [Log4J, Commons Codec, Xerces, XML APIs, spring-webmvc, spring-mock, JUnit, Servlet API, - Portlet API, JMX, Struts, JSF] + [Log4J, Xerces, XML APIs, spring-webmvc, spring-mock, JUnit, Servlet API, Portlet API, JMX, + Struts, JSF] * spring-binding-1.0.jar - Contents: The Spring Data Binding framework, an internal library used by SWF diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/ClientContinuationFlowExecutionRepository.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/ClientContinuationFlowExecutionRepository.java index 52b624c9..715eb3db 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/ClientContinuationFlowExecutionRepository.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/ClientContinuationFlowExecutionRepository.java @@ -17,7 +17,6 @@ package org.springframework.webflow.execution.repository.continuation; import java.io.Serializable; -import org.apache.commons.codec.binary.Base64; import org.springframework.util.Assert; import org.springframework.webflow.conversation.Conversation; import org.springframework.webflow.conversation.ConversationException; @@ -31,6 +30,7 @@ import org.springframework.webflow.execution.repository.FlowExecutionKey; import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException; import org.springframework.webflow.execution.repository.support.AbstractConversationFlowExecutionRepository; import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer; +import org.springframework.webflow.util.Base64; /** * Stores flow execution state client side, requiring no use of server-side @@ -63,10 +63,6 @@ import org.springframework.webflow.execution.repository.support.FlowExecutionSta * {@link #encode(FlowExecution)} and {@link #decode(String)}, implementing * them with a secure encoding/decoding algorithm, e.g. based on public/private * key encryption. - *

- * This class depends on the Jakarta Commons Codec library to do - * BASE64 encoding. Codec code must be available in the classpath - * when using this implementation. * * @see Base64 * @@ -171,7 +167,7 @@ public class ClientContinuationFlowExecutionRepository extends AbstractConversat */ protected Serializable encode(FlowExecution flowExecution) { FlowExecutionContinuation continuation = continuationFactory.createContinuation(flowExecution); - return new String(Base64.encodeBase64(continuation.toByteArray())); + return new Base64(true).encodeToString(continuation.toByteArray()); } /** @@ -185,7 +181,7 @@ public class ClientContinuationFlowExecutionRepository extends AbstractConversat * @return the decoded flow execution instance */ protected FlowExecutionContinuation decode(String encodedContinuation) { - byte[] bytes = Base64.decodeBase64(encodedContinuation.getBytes()); + byte[] bytes = new Base64(true).decodeFromString(encodedContinuation); return continuationFactory.createContinuation(bytes); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/package.html b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/package.html index 62dd8f40..4e44d241 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/package.html +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/continuation/package.html @@ -3,8 +3,5 @@

Implementation of continuation-based flow execution repositories.

-

-Usage of ClientContinuationFlowExecutionRepository requires Apache's commons-codec. -

\ No newline at end of file diff --git a/spring-webflow/src/main/java/org/springframework/webflow/util/Base64.java b/spring-webflow/src/main/java/org/springframework/webflow/util/Base64.java new file mode 100644 index 00000000..89355976 --- /dev/null +++ b/spring-webflow/src/main/java/org/springframework/webflow/util/Base64.java @@ -0,0 +1,444 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.util; + +/** + * Encodes and decodes to and from Base64 notation. + *

+ * Based on Base64 encoder and decoder version 2.2.1 written by Robert Harder + * (http://iharder.net/base64). + * Modified by Erwin Vervaet to use the '.' character as padding character + * when using URL safe encoding, like in the Bouncy Castle URLBase64 encoder + * (http://www.bouncycastle.org/java.html). + * + * @author Robert Harder + * @author Erwin Vervaet + */ +public class Base64 { + + /* static data used by the encoding and decoding algorithm + + /* The equals sign (=) as a byte. */ + private static final byte EQUALS_SIGN = (byte)'='; + /* The dot (.) as a byte. */ + private static final byte DOT = (byte)'.'; + + private static final byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private static final byte PADDING_CHAR_ENC = -1; // Indicates padding char in encoding + + + /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform may be something funny like EBCDIC, so we hardcode these values. */ + private static final byte[] STANDARD_ALPHABET = { + (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)'/' + }; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private static final byte[] STANDARD_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,-9,-9, // Decimal 44 - 46 + 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,-9,-9, // Decimal 91 - 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 + }; + + /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private static final byte[] URL_SAFE_ALPHABET = { + (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)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private static final byte[] URL_SAFE_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 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -1, // Dot at decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -9, // 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 + }; + + // instance members + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + */ + private boolean urlSafe; + + private byte[] ALPHABET; + private byte[] DECODABET; + private byte PADDING_CHAR; + + /** + * Create a new Base64 encoder and decoder using the standard Base64 alphabet. + * Note that the resulting encoded strings are not URL-safe: they will + * can contain characters that are subject to URL encoding. + */ + public Base64() { + this(false); + } + + /** + * Create a new Base64 encoder and decoder. + *

Allows Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * When URL-safe encoding is used, the standard "=" Base64 padding character is replaced + * with the '.' character. + *

+ * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect + * + * @param urlSafe if true, URL safe encoding and decoding will be used + */ + public Base64(boolean urlSafe) { + this.urlSafe = urlSafe; + if (urlSafe) { + ALPHABET = URL_SAFE_ALPHABET; + DECODABET = URL_SAFE_DECODABET; + PADDING_CHAR = DOT; + } + else { + ALPHABET = STANDARD_ALPHABET; + DECODABET = STANDARD_DECODABET; + PADDING_CHAR = EQUALS_SIGN; + } + } + + /** + * Returns whether or not this coder is using Base64-like encoding that is URL- and Filename-safe + * as described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * When URL-safe encoding is used, the standard "=" Base64 padding character is replaced + * with the '.' character. + *

+ * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + * @return true or false + */ + public boolean isUrlSafe() { + return urlSafe; + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * 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 accomodate 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.

+ *

This is the lowest level of the encoding methods with + * all possible parameters.

+ * @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 + */ + private 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 ] = PADDING_CHAR; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = PADDING_CHAR; + destination[ destOffset + 3 ] = PADDING_CHAR; + return destination; + + default: + return destination; + } + } + + /** + * Encodes a byte array into Base64 notation. + * @param source the data to convert + * @param off offset in array where conversion should begin + * @param len length of data to convert + * @return the encoded data + */ + public final byte[] encode(byte[] source, int off, int len) { + int len43 = len * 4 / 3; + byte[] outBuff = new byte[ ( len43 ) // main 4:3 + + ( (len % 3) > 0 ? 4 : 0 ) ]; // account for padding + 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; + } // end for: each piece of array + + if (d < len) { + encode3to4(source, d+off, len - d, outBuff, e); + e += 4; + } // end if: some padding needed + + byte[] out = new byte[e]; + System.arraycopy(outBuff, 0, out, 0, e); + return out; + } + + /** + * Encodes a byte array into Base64 notation. + * @param source the data to encode + * @return the encoded data + */ + public final byte[] encode(byte[] source) { + return encode(source, 0, source.length); + } + + /** + * Encodes a byte array into Base64 notation. The resulting string will + * be created using the platform default encoding. + * @param source the source data to encode + * @return the encoded data + */ + public final String encodeToString(byte[] source) { + return new String(encode(source)); + } + + /* ******** D E C O D I N G M 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 accomodate 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. + *

This is the lowest level of the decoding methods with + * all possible parameters.

+ * @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 + */ + private final int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) { + // Example: Dk== or Dk.. + if (source[ srcOffset + 2] == PADDING_CHAR) { + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= or DkL. + else if (source[ srcOffset + 3 ] == PADDING_CHAR) { + 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 { + 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; + } + } + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. + * @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 + */ + public final 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 >= PADDING_CHAR_ENC) { + b4[b4Posn++] = sbiCrop; + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); + b4Posn = 0; + + // if that was the padding char, break out of 'for' loop + if (sbiCrop == PADDING_CHAR) { + break; + } + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + //discard + } + } // each input character + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } + + /** + * Decodes data from Base64 notation. + * @param source the source data + * @return the decoded data + */ + public final byte[] decode(byte[] source) { + return decode(source, 0, source.length); + } + + /** + * Decodes data from Base64 notation. Uses the platform default + * character set to obtain bytes from given string. + * @param s the string to decode + * @return the decoded data + */ + public final byte[] decodeFromString(String s) { + return decode(s.getBytes()); + } +} diff --git a/spring-webflow/src/test/java/org/springframework/webflow/util/Base64Tests.java b/spring-webflow/src/test/java/org/springframework/webflow/util/Base64Tests.java new file mode 100644 index 00000000..04c92363 --- /dev/null +++ b/spring-webflow/src/test/java/org/springframework/webflow/util/Base64Tests.java @@ -0,0 +1,555 @@ +/* + * Copyright 2004-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.webflow.util; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Random; + +import junit.framework.TestCase; + +/** + * Test case for {@link Base64}. + * Based on Base64Test from Jakarta Commons Codec 1.3. + * + * @author Erwin Vervaet + */ +public class Base64Tests extends TestCase { + + private Random random = new Random(); + + public Random getRandom() { + return this.random; + } + + public void testBase64() { + assertEquals("SGVsbG8gV29ybGQ=", new Base64().encodeToString("Hello World".getBytes())); + assertEquals("SGVsbG8gV29ybGQ.", new Base64(true).encodeToString("Hello World".getBytes())); + } + + public void testDecodePadMarkerIndex2() { + assertEquals("A", new String(new Base64().decodeFromString("QQ=="))); + assertEquals("A", new String(new Base64(true).decodeFromString("QQ.."))); + } + + public void testDecodePadMarkerIndex3() { + assertEquals("AA", new String(new Base64().decodeFromString("QUE="))); + assertEquals("AAA", new String(new Base64().decodeFromString("QUFB"))); + assertEquals("AA", new String(new Base64(true).decodeFromString("QUE."))); + assertEquals("AAA", new String(new Base64(true).decodeFromString("QUFB"))); + } + + public void testEncodeEmptyString() { + assertEquals("", new Base64().encodeToString("".getBytes())); + } + + public void testDecodeEmptyString() { + assertEquals("", new String(new Base64().decodeFromString(""))); + } + + // encode/decode random arrays from size 0 to size 11 + public void testEncodeDecodeSmall() { + for (int i = 0; i < 12; i++) { + byte[] data = new byte[i]; + this.getRandom().nextBytes(data); + byte[] enc = new Base64().encode(data); + byte[] data2 = new Base64().decode(enc); + assertTrue(Arrays.equals(data,data2)); + } + } + + // encode/decode a large random array + public void testEncodeDecodeRandom() { + for(int i = 1;i < 5; i++) { + byte[] data = new byte[this.getRandom().nextInt(10000) + 1]; + this.getRandom().nextBytes(data); + byte[] enc = new Base64().encode(data); + byte[] data2 = new Base64().decode(enc); + assertTrue(Arrays.equals(data,data2)); + } + } + + public void testSingletons() { + assertEquals("AA==", new String(new Base64().encode(new byte[] { (byte)0 }))); + assertEquals("AQ==", new String(new Base64().encode(new byte[] { (byte)1 }))); + assertEquals("Ag==", new String(new Base64().encode(new byte[] { (byte)2 }))); + assertEquals("Aw==", new String(new Base64().encode(new byte[] { (byte)3 }))); + assertEquals("BA==", new String(new Base64().encode(new byte[] { (byte)4 }))); + assertEquals("BQ==", new String(new Base64().encode(new byte[] { (byte)5 }))); + assertEquals("Bg==", new String(new Base64().encode(new byte[] { (byte)6 }))); + assertEquals("Bw==", new String(new Base64().encode(new byte[] { (byte)7 }))); + assertEquals("CA==", new String(new Base64().encode(new byte[] { (byte)8 }))); + assertEquals("CQ==", new String(new Base64().encode(new byte[] { (byte)9 }))); + assertEquals("Cg==", new String(new Base64().encode(new byte[] { (byte)10 }))); + assertEquals("Cw==", new String(new Base64().encode(new byte[] { (byte)11 }))); + assertEquals("DA==", new String(new Base64().encode(new byte[] { (byte)12 }))); + assertEquals("DQ==", new String(new Base64().encode(new byte[] { (byte)13 }))); + assertEquals("Dg==", new String(new Base64().encode(new byte[] { (byte)14 }))); + assertEquals("Dw==", new String(new Base64().encode(new byte[] { (byte)15 }))); + assertEquals("EA==", new String(new Base64().encode(new byte[] { (byte)16 }))); + assertEquals("EQ==", new String(new Base64().encode(new byte[] { (byte)17 }))); + assertEquals("Eg==", new String(new Base64().encode(new byte[] { (byte)18 }))); + assertEquals("Ew==", new String(new Base64().encode(new byte[] { (byte)19 }))); + assertEquals("FA==", new String(new Base64().encode(new byte[] { (byte)20 }))); + assertEquals("FQ==", new String(new Base64().encode(new byte[] { (byte)21 }))); + assertEquals("Fg==", new String(new Base64().encode(new byte[] { (byte)22 }))); + assertEquals("Fw==", new String(new Base64().encode(new byte[] { (byte)23 }))); + assertEquals("GA==", new String(new Base64().encode(new byte[] { (byte)24 }))); + assertEquals("GQ==", new String(new Base64().encode(new byte[] { (byte)25 }))); + assertEquals("Gg==", new String(new Base64().encode(new byte[] { (byte)26 }))); + assertEquals("Gw==", new String(new Base64().encode(new byte[] { (byte)27 }))); + assertEquals("HA==", new String(new Base64().encode(new byte[] { (byte)28 }))); + assertEquals("HQ==", new String(new Base64().encode(new byte[] { (byte)29 }))); + assertEquals("Hg==", new String(new Base64().encode(new byte[] { (byte)30 }))); + assertEquals("Hw==", new String(new Base64().encode(new byte[] { (byte)31 }))); + assertEquals("IA==", new String(new Base64().encode(new byte[] { (byte)32 }))); + assertEquals("IQ==", new String(new Base64().encode(new byte[] { (byte)33 }))); + assertEquals("Ig==", new String(new Base64().encode(new byte[] { (byte)34 }))); + assertEquals("Iw==", new String(new Base64().encode(new byte[] { (byte)35 }))); + assertEquals("JA==", new String(new Base64().encode(new byte[] { (byte)36 }))); + assertEquals("JQ==", new String(new Base64().encode(new byte[] { (byte)37 }))); + assertEquals("Jg==", new String(new Base64().encode(new byte[] { (byte)38 }))); + assertEquals("Jw==", new String(new Base64().encode(new byte[] { (byte)39 }))); + assertEquals("KA==", new String(new Base64().encode(new byte[] { (byte)40 }))); + assertEquals("KQ==", new String(new Base64().encode(new byte[] { (byte)41 }))); + assertEquals("Kg==", new String(new Base64().encode(new byte[] { (byte)42 }))); + assertEquals("Kw==", new String(new Base64().encode(new byte[] { (byte)43 }))); + assertEquals("LA==", new String(new Base64().encode(new byte[] { (byte)44 }))); + assertEquals("LQ==", new String(new Base64().encode(new byte[] { (byte)45 }))); + assertEquals("Lg==", new String(new Base64().encode(new byte[] { (byte)46 }))); + assertEquals("Lw==", new String(new Base64().encode(new byte[] { (byte)47 }))); + assertEquals("MA==", new String(new Base64().encode(new byte[] { (byte)48 }))); + assertEquals("MQ==", new String(new Base64().encode(new byte[] { (byte)49 }))); + assertEquals("Mg==", new String(new Base64().encode(new byte[] { (byte)50 }))); + assertEquals("Mw==", new String(new Base64().encode(new byte[] { (byte)51 }))); + assertEquals("NA==", new String(new Base64().encode(new byte[] { (byte)52 }))); + assertEquals("NQ==", new String(new Base64().encode(new byte[] { (byte)53 }))); + assertEquals("Ng==", new String(new Base64().encode(new byte[] { (byte)54 }))); + assertEquals("Nw==", new String(new Base64().encode(new byte[] { (byte)55 }))); + assertEquals("OA==", new String(new Base64().encode(new byte[] { (byte)56 }))); + assertEquals("OQ==", new String(new Base64().encode(new byte[] { (byte)57 }))); + assertEquals("Og==", new String(new Base64().encode(new byte[] { (byte)58 }))); + assertEquals("Ow==", new String(new Base64().encode(new byte[] { (byte)59 }))); + assertEquals("PA==", new String(new Base64().encode(new byte[] { (byte)60 }))); + assertEquals("PQ==", new String(new Base64().encode(new byte[] { (byte)61 }))); + assertEquals("Pg==", new String(new Base64().encode(new byte[] { (byte)62 }))); + assertEquals("Pw==", new String(new Base64().encode(new byte[] { (byte)63 }))); + assertEquals("QA==", new String(new Base64().encode(new byte[] { (byte)64 }))); + assertEquals("QQ==", new String(new Base64().encode(new byte[] { (byte)65 }))); + assertEquals("Qg==", new String(new Base64().encode(new byte[] { (byte)66 }))); + assertEquals("Qw==", new String(new Base64().encode(new byte[] { (byte)67 }))); + assertEquals("RA==", new String(new Base64().encode(new byte[] { (byte)68 }))); + assertEquals("RQ==", new String(new Base64().encode(new byte[] { (byte)69 }))); + assertEquals("Rg==", new String(new Base64().encode(new byte[] { (byte)70 }))); + assertEquals("Rw==", new String(new Base64().encode(new byte[] { (byte)71 }))); + assertEquals("SA==", new String(new Base64().encode(new byte[] { (byte)72 }))); + assertEquals("SQ==", new String(new Base64().encode(new byte[] { (byte)73 }))); + assertEquals("Sg==", new String(new Base64().encode(new byte[] { (byte)74 }))); + assertEquals("Sw==", new String(new Base64().encode(new byte[] { (byte)75 }))); + assertEquals("TA==", new String(new Base64().encode(new byte[] { (byte)76 }))); + assertEquals("TQ==", new String(new Base64().encode(new byte[] { (byte)77 }))); + assertEquals("Tg==", new String(new Base64().encode(new byte[] { (byte)78 }))); + assertEquals("Tw==", new String(new Base64().encode(new byte[] { (byte)79 }))); + assertEquals("UA==", new String(new Base64().encode(new byte[] { (byte)80 }))); + assertEquals("UQ==", new String(new Base64().encode(new byte[] { (byte)81 }))); + assertEquals("Ug==", new String(new Base64().encode(new byte[] { (byte)82 }))); + assertEquals("Uw==", new String(new Base64().encode(new byte[] { (byte)83 }))); + assertEquals("VA==", new String(new Base64().encode(new byte[] { (byte)84 }))); + assertEquals("VQ==", new String(new Base64().encode(new byte[] { (byte)85 }))); + assertEquals("Vg==", new String(new Base64().encode(new byte[] { (byte)86 }))); + assertEquals("Vw==", new String(new Base64().encode(new byte[] { (byte)87 }))); + assertEquals("WA==", new String(new Base64().encode(new byte[] { (byte)88 }))); + assertEquals("WQ==", new String(new Base64().encode(new byte[] { (byte)89 }))); + assertEquals("Wg==", new String(new Base64().encode(new byte[] { (byte)90 }))); + assertEquals("Ww==", new String(new Base64().encode(new byte[] { (byte)91 }))); + assertEquals("XA==", new String(new Base64().encode(new byte[] { (byte)92 }))); + assertEquals("XQ==", new String(new Base64().encode(new byte[] { (byte)93 }))); + assertEquals("Xg==", new String(new Base64().encode(new byte[] { (byte)94 }))); + assertEquals("Xw==", new String(new Base64().encode(new byte[] { (byte)95 }))); + assertEquals("YA==", new String(new Base64().encode(new byte[] { (byte)96 }))); + assertEquals("YQ==", new String(new Base64().encode(new byte[] { (byte)97 }))); + assertEquals("Yg==", new String(new Base64().encode(new byte[] { (byte)98 }))); + assertEquals("Yw==", new String(new Base64().encode(new byte[] { (byte)99 }))); + assertEquals("ZA==", new String(new Base64().encode(new byte[] { (byte)100 }))); + assertEquals("ZQ==", new String(new Base64().encode(new byte[] { (byte)101 }))); + assertEquals("Zg==", new String(new Base64().encode(new byte[] { (byte)102 }))); + assertEquals("Zw==", new String(new Base64().encode(new byte[] { (byte)103 }))); + assertEquals("aA==", new String(new Base64().encode(new byte[] { (byte)104 }))); + + assertEquals("AA..", new String(new Base64(true).encode(new byte[] { (byte)0 }))); + assertEquals("AQ..", new String(new Base64(true).encode(new byte[] { (byte)1 }))); + assertEquals("Ag..", new String(new Base64(true).encode(new byte[] { (byte)2 }))); + assertEquals("Aw..", new String(new Base64(true).encode(new byte[] { (byte)3 }))); + assertEquals("BA..", new String(new Base64(true).encode(new byte[] { (byte)4 }))); + assertEquals("BQ..", new String(new Base64(true).encode(new byte[] { (byte)5 }))); + assertEquals("Bg..", new String(new Base64(true).encode(new byte[] { (byte)6 }))); + assertEquals("Bw..", new String(new Base64(true).encode(new byte[] { (byte)7 }))); + assertEquals("CA..", new String(new Base64(true).encode(new byte[] { (byte)8 }))); + assertEquals("CQ..", new String(new Base64(true).encode(new byte[] { (byte)9 }))); + assertEquals("Cg..", new String(new Base64(true).encode(new byte[] { (byte)10 }))); + assertEquals("Cw..", new String(new Base64(true).encode(new byte[] { (byte)11 }))); + assertEquals("DA..", new String(new Base64(true).encode(new byte[] { (byte)12 }))); + assertEquals("DQ..", new String(new Base64(true).encode(new byte[] { (byte)13 }))); + assertEquals("Dg..", new String(new Base64(true).encode(new byte[] { (byte)14 }))); + assertEquals("Dw..", new String(new Base64(true).encode(new byte[] { (byte)15 }))); + assertEquals("EA..", new String(new Base64(true).encode(new byte[] { (byte)16 }))); + assertEquals("EQ..", new String(new Base64(true).encode(new byte[] { (byte)17 }))); + assertEquals("Eg..", new String(new Base64(true).encode(new byte[] { (byte)18 }))); + assertEquals("Ew..", new String(new Base64(true).encode(new byte[] { (byte)19 }))); + assertEquals("FA..", new String(new Base64(true).encode(new byte[] { (byte)20 }))); + assertEquals("FQ..", new String(new Base64(true).encode(new byte[] { (byte)21 }))); + assertEquals("Fg..", new String(new Base64(true).encode(new byte[] { (byte)22 }))); + assertEquals("Fw..", new String(new Base64(true).encode(new byte[] { (byte)23 }))); + assertEquals("GA..", new String(new Base64(true).encode(new byte[] { (byte)24 }))); + assertEquals("GQ..", new String(new Base64(true).encode(new byte[] { (byte)25 }))); + assertEquals("Gg..", new String(new Base64(true).encode(new byte[] { (byte)26 }))); + assertEquals("Gw..", new String(new Base64(true).encode(new byte[] { (byte)27 }))); + assertEquals("HA..", new String(new Base64(true).encode(new byte[] { (byte)28 }))); + assertEquals("HQ..", new String(new Base64(true).encode(new byte[] { (byte)29 }))); + assertEquals("Hg..", new String(new Base64(true).encode(new byte[] { (byte)30 }))); + assertEquals("Hw..", new String(new Base64(true).encode(new byte[] { (byte)31 }))); + assertEquals("IA..", new String(new Base64(true).encode(new byte[] { (byte)32 }))); + assertEquals("IQ..", new String(new Base64(true).encode(new byte[] { (byte)33 }))); + assertEquals("Ig..", new String(new Base64(true).encode(new byte[] { (byte)34 }))); + assertEquals("Iw..", new String(new Base64(true).encode(new byte[] { (byte)35 }))); + assertEquals("JA..", new String(new Base64(true).encode(new byte[] { (byte)36 }))); + assertEquals("JQ..", new String(new Base64(true).encode(new byte[] { (byte)37 }))); + assertEquals("Jg..", new String(new Base64(true).encode(new byte[] { (byte)38 }))); + assertEquals("Jw..", new String(new Base64(true).encode(new byte[] { (byte)39 }))); + assertEquals("KA..", new String(new Base64(true).encode(new byte[] { (byte)40 }))); + assertEquals("KQ..", new String(new Base64(true).encode(new byte[] { (byte)41 }))); + assertEquals("Kg..", new String(new Base64(true).encode(new byte[] { (byte)42 }))); + assertEquals("Kw..", new String(new Base64(true).encode(new byte[] { (byte)43 }))); + assertEquals("LA..", new String(new Base64(true).encode(new byte[] { (byte)44 }))); + assertEquals("LQ..", new String(new Base64(true).encode(new byte[] { (byte)45 }))); + assertEquals("Lg..", new String(new Base64(true).encode(new byte[] { (byte)46 }))); + assertEquals("Lw..", new String(new Base64(true).encode(new byte[] { (byte)47 }))); + assertEquals("MA..", new String(new Base64(true).encode(new byte[] { (byte)48 }))); + assertEquals("MQ..", new String(new Base64(true).encode(new byte[] { (byte)49 }))); + assertEquals("Mg..", new String(new Base64(true).encode(new byte[] { (byte)50 }))); + assertEquals("Mw..", new String(new Base64(true).encode(new byte[] { (byte)51 }))); + assertEquals("NA..", new String(new Base64(true).encode(new byte[] { (byte)52 }))); + assertEquals("NQ..", new String(new Base64(true).encode(new byte[] { (byte)53 }))); + assertEquals("Ng..", new String(new Base64(true).encode(new byte[] { (byte)54 }))); + assertEquals("Nw..", new String(new Base64(true).encode(new byte[] { (byte)55 }))); + assertEquals("OA..", new String(new Base64(true).encode(new byte[] { (byte)56 }))); + assertEquals("OQ..", new String(new Base64(true).encode(new byte[] { (byte)57 }))); + assertEquals("Og..", new String(new Base64(true).encode(new byte[] { (byte)58 }))); + assertEquals("Ow..", new String(new Base64(true).encode(new byte[] { (byte)59 }))); + assertEquals("PA..", new String(new Base64(true).encode(new byte[] { (byte)60 }))); + assertEquals("PQ..", new String(new Base64(true).encode(new byte[] { (byte)61 }))); + assertEquals("Pg..", new String(new Base64(true).encode(new byte[] { (byte)62 }))); + assertEquals("Pw..", new String(new Base64(true).encode(new byte[] { (byte)63 }))); + assertEquals("QA..", new String(new Base64(true).encode(new byte[] { (byte)64 }))); + assertEquals("QQ..", new String(new Base64(true).encode(new byte[] { (byte)65 }))); + assertEquals("Qg..", new String(new Base64(true).encode(new byte[] { (byte)66 }))); + assertEquals("Qw..", new String(new Base64(true).encode(new byte[] { (byte)67 }))); + assertEquals("RA..", new String(new Base64(true).encode(new byte[] { (byte)68 }))); + assertEquals("RQ..", new String(new Base64(true).encode(new byte[] { (byte)69 }))); + assertEquals("Rg..", new String(new Base64(true).encode(new byte[] { (byte)70 }))); + assertEquals("Rw..", new String(new Base64(true).encode(new byte[] { (byte)71 }))); + assertEquals("SA..", new String(new Base64(true).encode(new byte[] { (byte)72 }))); + assertEquals("SQ..", new String(new Base64(true).encode(new byte[] { (byte)73 }))); + assertEquals("Sg..", new String(new Base64(true).encode(new byte[] { (byte)74 }))); + assertEquals("Sw..", new String(new Base64(true).encode(new byte[] { (byte)75 }))); + assertEquals("TA..", new String(new Base64(true).encode(new byte[] { (byte)76 }))); + assertEquals("TQ..", new String(new Base64(true).encode(new byte[] { (byte)77 }))); + assertEquals("Tg..", new String(new Base64(true).encode(new byte[] { (byte)78 }))); + assertEquals("Tw..", new String(new Base64(true).encode(new byte[] { (byte)79 }))); + assertEquals("UA..", new String(new Base64(true).encode(new byte[] { (byte)80 }))); + assertEquals("UQ..", new String(new Base64(true).encode(new byte[] { (byte)81 }))); + assertEquals("Ug..", new String(new Base64(true).encode(new byte[] { (byte)82 }))); + assertEquals("Uw..", new String(new Base64(true).encode(new byte[] { (byte)83 }))); + assertEquals("VA..", new String(new Base64(true).encode(new byte[] { (byte)84 }))); + assertEquals("VQ..", new String(new Base64(true).encode(new byte[] { (byte)85 }))); + assertEquals("Vg..", new String(new Base64(true).encode(new byte[] { (byte)86 }))); + assertEquals("Vw..", new String(new Base64(true).encode(new byte[] { (byte)87 }))); + assertEquals("WA..", new String(new Base64(true).encode(new byte[] { (byte)88 }))); + assertEquals("WQ..", new String(new Base64(true).encode(new byte[] { (byte)89 }))); + assertEquals("Wg..", new String(new Base64(true).encode(new byte[] { (byte)90 }))); + assertEquals("Ww..", new String(new Base64(true).encode(new byte[] { (byte)91 }))); + assertEquals("XA..", new String(new Base64(true).encode(new byte[] { (byte)92 }))); + assertEquals("XQ..", new String(new Base64(true).encode(new byte[] { (byte)93 }))); + assertEquals("Xg..", new String(new Base64(true).encode(new byte[] { (byte)94 }))); + assertEquals("Xw..", new String(new Base64(true).encode(new byte[] { (byte)95 }))); + assertEquals("YA..", new String(new Base64(true).encode(new byte[] { (byte)96 }))); + assertEquals("YQ..", new String(new Base64(true).encode(new byte[] { (byte)97 }))); + assertEquals("Yg..", new String(new Base64(true).encode(new byte[] { (byte)98 }))); + assertEquals("Yw..", new String(new Base64(true).encode(new byte[] { (byte)99 }))); + assertEquals("ZA..", new String(new Base64(true).encode(new byte[] { (byte)100 }))); + assertEquals("ZQ..", new String(new Base64(true).encode(new byte[] { (byte)101 }))); + assertEquals("Zg..", new String(new Base64(true).encode(new byte[] { (byte)102 }))); + assertEquals("Zw..", new String(new Base64(true).encode(new byte[] { (byte)103 }))); + assertEquals("aA..", new String(new Base64(true).encode(new byte[] { (byte)104 }))); + } + + public void testTriplets() { + assertEquals("AAAA", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)0 }))); + assertEquals("AAAB", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)1 }))); + assertEquals("AAAC", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)2 }))); + assertEquals("AAAD", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)3 }))); + assertEquals("AAAE", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)4 }))); + assertEquals("AAAF", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)5 }))); + assertEquals("AAAG", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)6 }))); + assertEquals("AAAH", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)7 }))); + assertEquals("AAAI", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)8 }))); + assertEquals("AAAJ", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)9 }))); + assertEquals("AAAK", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)10 }))); + assertEquals("AAAL", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)11 }))); + assertEquals("AAAM", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)12 }))); + assertEquals("AAAN", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)13 }))); + assertEquals("AAAO", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)14 }))); + assertEquals("AAAP", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)15 }))); + assertEquals("AAAQ", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)16 }))); + assertEquals("AAAR", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)17 }))); + assertEquals("AAAS", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)18 }))); + assertEquals("AAAT", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)19 }))); + assertEquals("AAAU", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)20 }))); + assertEquals("AAAV", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)21 }))); + assertEquals("AAAW", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)22 }))); + assertEquals("AAAX", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)23 }))); + assertEquals("AAAY", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)24 }))); + assertEquals("AAAZ", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)25 }))); + assertEquals("AAAa", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)26 }))); + assertEquals("AAAb", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)27 }))); + assertEquals("AAAc", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)28 }))); + assertEquals("AAAd", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)29 }))); + assertEquals("AAAe", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)30 }))); + assertEquals("AAAf", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)31 }))); + assertEquals("AAAg", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)32 }))); + assertEquals("AAAh", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)33 }))); + assertEquals("AAAi", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)34 }))); + assertEquals("AAAj", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)35 }))); + assertEquals("AAAk", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)36 }))); + assertEquals("AAAl", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)37 }))); + assertEquals("AAAm", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)38 }))); + assertEquals("AAAn", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)39 }))); + assertEquals("AAAo", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)40 }))); + assertEquals("AAAp", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)41 }))); + assertEquals("AAAq", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)42 }))); + assertEquals("AAAr", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)43 }))); + assertEquals("AAAs", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)44 }))); + assertEquals("AAAt", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)45 }))); + assertEquals("AAAu", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)46 }))); + assertEquals("AAAv", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)47 }))); + assertEquals("AAAw", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)48 }))); + assertEquals("AAAx", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)49 }))); + assertEquals("AAAy", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)50 }))); + assertEquals("AAAz", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)51 }))); + assertEquals("AAA0", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)52 }))); + assertEquals("AAA1", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)53 }))); + assertEquals("AAA2", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)54 }))); + assertEquals("AAA3", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)55 }))); + assertEquals("AAA4", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)56 }))); + assertEquals("AAA5", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)57 }))); + assertEquals("AAA6", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)58 }))); + assertEquals("AAA7", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)59 }))); + assertEquals("AAA8", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)60 }))); + assertEquals("AAA9", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)61 }))); + assertEquals("AAA+", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)62 }))); + assertEquals("AAA/", new String(new Base64().encode(new byte[] { (byte)0, (byte)0, (byte)63 }))); + + assertEquals("AAAA", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)0 }))); + assertEquals("AAAB", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)1 }))); + assertEquals("AAAC", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)2 }))); + assertEquals("AAAD", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)3 }))); + assertEquals("AAAE", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)4 }))); + assertEquals("AAAF", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)5 }))); + assertEquals("AAAG", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)6 }))); + assertEquals("AAAH", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)7 }))); + assertEquals("AAAI", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)8 }))); + assertEquals("AAAJ", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)9 }))); + assertEquals("AAAK", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)10 }))); + assertEquals("AAAL", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)11 }))); + assertEquals("AAAM", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)12 }))); + assertEquals("AAAN", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)13 }))); + assertEquals("AAAO", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)14 }))); + assertEquals("AAAP", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)15 }))); + assertEquals("AAAQ", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)16 }))); + assertEquals("AAAR", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)17 }))); + assertEquals("AAAS", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)18 }))); + assertEquals("AAAT", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)19 }))); + assertEquals("AAAU", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)20 }))); + assertEquals("AAAV", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)21 }))); + assertEquals("AAAW", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)22 }))); + assertEquals("AAAX", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)23 }))); + assertEquals("AAAY", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)24 }))); + assertEquals("AAAZ", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)25 }))); + assertEquals("AAAa", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)26 }))); + assertEquals("AAAb", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)27 }))); + assertEquals("AAAc", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)28 }))); + assertEquals("AAAd", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)29 }))); + assertEquals("AAAe", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)30 }))); + assertEquals("AAAf", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)31 }))); + assertEquals("AAAg", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)32 }))); + assertEquals("AAAh", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)33 }))); + assertEquals("AAAi", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)34 }))); + assertEquals("AAAj", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)35 }))); + assertEquals("AAAk", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)36 }))); + assertEquals("AAAl", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)37 }))); + assertEquals("AAAm", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)38 }))); + assertEquals("AAAn", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)39 }))); + assertEquals("AAAo", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)40 }))); + assertEquals("AAAp", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)41 }))); + assertEquals("AAAq", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)42 }))); + assertEquals("AAAr", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)43 }))); + assertEquals("AAAs", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)44 }))); + assertEquals("AAAt", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)45 }))); + assertEquals("AAAu", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)46 }))); + assertEquals("AAAv", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)47 }))); + assertEquals("AAAw", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)48 }))); + assertEquals("AAAx", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)49 }))); + assertEquals("AAAy", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)50 }))); + assertEquals("AAAz", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)51 }))); + assertEquals("AAA0", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)52 }))); + assertEquals("AAA1", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)53 }))); + assertEquals("AAA2", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)54 }))); + assertEquals("AAA3", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)55 }))); + assertEquals("AAA4", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)56 }))); + assertEquals("AAA5", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)57 }))); + assertEquals("AAA6", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)58 }))); + assertEquals("AAA7", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)59 }))); + assertEquals("AAA8", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)60 }))); + assertEquals("AAA9", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)61 }))); + assertEquals("AAA-", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)62 }))); + assertEquals("AAA_", new String(new Base64(true).encode(new byte[] { (byte)0, (byte)0, (byte)63 }))); + } + + public void testKnownEncodings() { + assertEquals("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg==", + new Base64().encodeToString("The quick brown fox jumped over the lazy dogs.".getBytes())); + assertEquals("SXQgd2FzIHRoZSBiZXN0IG9mIHRpbWVzLCBpdCB3YXMgdGhlIHdvcnN0IG9mIHRpbWVzLg==", + new Base64().encodeToString("It was the best of times, it was the worst of times.".getBytes())); + assertEquals("aHR0cDovL2pha2FydGEuYXBhY2hlLm9yZy9jb21tbW9ucw==", + new Base64().encodeToString("http://jakarta.apache.org/commmons".getBytes())); + assertEquals("QWFCYkNjRGRFZUZmR2dIaElpSmpLa0xsTW1Obk9vUHBRcVJyU3NUdFV1VnZXd1h4WXlaeg==", + new Base64().encodeToString("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz".getBytes())); + assertEquals("eyAwLCAxLCAyLCAzLCA0LCA1LCA2LCA3LCA4LCA5IH0=", + new Base64().encodeToString("{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }".getBytes())); + assertEquals("eHl6enkh", new String(new Base64().encode("xyzzy!".getBytes()))); + + assertEquals("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg..", + new Base64(true).encodeToString("The quick brown fox jumped over the lazy dogs.".getBytes())); + assertEquals("SXQgd2FzIHRoZSBiZXN0IG9mIHRpbWVzLCBpdCB3YXMgdGhlIHdvcnN0IG9mIHRpbWVzLg..", + new Base64(true).encodeToString("It was the best of times, it was the worst of times.".getBytes())); + assertEquals("aHR0cDovL2pha2FydGEuYXBhY2hlLm9yZy9jb21tbW9ucw..", + new Base64(true).encodeToString("http://jakarta.apache.org/commmons".getBytes())); + assertEquals("QWFCYkNjRGRFZUZmR2dIaElpSmpLa0xsTW1Obk9vUHBRcVJyU3NUdFV1VnZXd1h4WXlaeg..", + new Base64(true).encodeToString("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz".getBytes())); + assertEquals("eyAwLCAxLCAyLCAzLCA0LCA1LCA2LCA3LCA4LCA5IH0.", + new Base64(true).encodeToString("{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }".getBytes())); + assertEquals("eHl6enkh", new String(new Base64(true).encode("xyzzy!".getBytes()))); + } + + public void testKnownDecodings() { + assertEquals("The quick brown fox jumped over the lazy dogs.", + new String(new Base64().decodeFromString("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg=="))); + assertEquals("It was the best of times, it was the worst of times.", + new String(new Base64().decodeFromString("SXQgd2FzIHRoZSBiZXN0IG9mIHRpbWVzLCBpdCB3YXMgdGhlIHdvcnN0IG9mIHRpbWVzLg=="))); + assertEquals("http://jakarta.apache.org/commmons", + new String(new Base64().decodeFromString("aHR0cDovL2pha2FydGEuYXBhY2hlLm9yZy9jb21tbW9ucw=="))); + assertEquals("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", + new String(new Base64().decodeFromString("QWFCYkNjRGRFZUZmR2dIaElpSmpLa0xsTW1Obk9vUHBRcVJyU3NUdFV1VnZXd1h4WXlaeg=="))); + assertEquals("{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }", + new String(new Base64().decodeFromString("eyAwLCAxLCAyLCAzLCA0LCA1LCA2LCA3LCA4LCA5IH0="))); + assertEquals("xyzzy!", + new String(new Base64().decodeFromString("eHl6enkh"))); + + assertEquals("The quick brown fox jumped over the lazy dogs.", + new String(new Base64(true).decodeFromString("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg.."))); + assertEquals("It was the best of times, it was the worst of times.", + new String(new Base64(true).decodeFromString("SXQgd2FzIHRoZSBiZXN0IG9mIHRpbWVzLCBpdCB3YXMgdGhlIHdvcnN0IG9mIHRpbWVzLg.."))); + assertEquals("http://jakarta.apache.org/commmons", + new String(new Base64(true).decodeFromString("aHR0cDovL2pha2FydGEuYXBhY2hlLm9yZy9jb21tbW9ucw.."))); + assertEquals("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz", + new String(new Base64(true).decodeFromString("QWFCYkNjRGRFZUZmR2dIaElpSmpLa0xsTW1Obk9vUHBRcVJyU3NUdFV1VnZXd1h4WXlaeg.."))); + assertEquals("{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }", + new String(new Base64(true).decodeFromString("eyAwLCAxLCAyLCAzLCA0LCA1LCA2LCA3LCA4LCA5IH0."))); + assertEquals("xyzzy!", + new String(new Base64(true).decodeFromString("eHl6enkh"))); + } + + public void testNonBase64Test() throws Exception { + byte[] bArray = { '%' }; + + try { + byte[] result = new Base64().decode(bArray); + assertTrue("The result should be empty as the test encoded content did " + + "not contain any valid base 64 characters", result.length == 0); + } + catch (Exception e) { + fail("Exception was thrown when trying to decode " + + "invalid base64 encoded data - RFC 2045 requires that all " + + "non base64 character be discarded, an exception should not" + + " have been thrown"); + } + } + + public void testIgnoringNonBase64InDecode() throws Exception { + assertEquals("The quick brown fox jumped over the lazy dogs.", + new String(new Base64().decodeFromString( + "VGhlIH@$#$@%F1aWN@#@#@@rIGJyb3duIGZve\n\r\t%#%#%#%CBqd##$#$W1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg=="))); + + assertEquals("The quick brown fox jumped over the lazy dogs.", + new String(new Base64(true).decodeFromString( + "VGhlIH@$#$@%F1aWN@#@#@@rIGJyb3duIGZve\n\r\t%#%#%#%CBqd##$#$W1wZWQgb3ZlciB0aGUgbGF6eSBkb2dzLg.."))); + } + + public void testDecodeWithWhitespace() throws Exception { + String orig = "I am a late night coder."; + + byte[] encodedArray = new Base64().encode(orig.getBytes()); + + StringBuffer intermediate = new StringBuffer(new String(encodedArray)); + intermediate.insert( 2, ' ' ); + intermediate.insert( 5, '\t' ); + intermediate.insert( 10, '\r' ); + intermediate.insert( 15, '\n' ); + + byte[] encodedWithWhitespace = intermediate.toString().getBytes(); + byte[] decodedWithWhitespace = new Base64().decode(encodedWithWhitespace); + + String dest = new String(decodedWithWhitespace); + + assertEquals(orig, dest); + } + + public void testEncodePaddingChar() { + assertEquals("=", new String(new Base64().decode(new Base64().encode("=".getBytes())))); + assertEquals(".", new String(new Base64().decode(new Base64().encode(".".getBytes())))); + assertEquals("=", new String(new Base64(true).decode(new Base64(true).encode("=".getBytes())))); + assertEquals(".", new String(new Base64(true).decode(new Base64(true).encode(".".getBytes())))); + } + + public void testUrlSafety() throws Exception { + byte[] bytes = new byte[256]; + for (int i = 0; i < 256; i++) { + bytes[i] = (byte)i; + } + byte[] encoded = new Base64(true).encode(bytes); + String encodedString = new String(encoded, "UTF-8"); + String urlEncoded = URLEncoder.encode(encodedString, "UTF-8"); + assertEquals(encodedString, urlEncoded); + String urlDecoded = URLDecoder.decode(urlEncoded, "UTF-8"); + assertEquals(encodedString, urlDecoded); + byte[] decoded = new Base64(true).decode(urlDecoded.getBytes("UTF-8")); + assertEquals(bytes.length, decoded.length); + for (int i = 0; i < 256; i++) { + assertEquals(bytes[i], decoded[i]); + } + } +} \ No newline at end of file