Switch to Base64 encoder/decoder from Commons Codec.
Performance testing shows it to be ~25-30x slower than Tomcat's implementation.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc7.0.x/trunk@1459346 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/java/org/apache/catalina/ant/AbstractCatalinaTask.java b/java/org/apache/catalina/ant/AbstractCatalinaTask.java
index f9e7f75..1e1b98b 100644
--- a/java/org/apache/catalina/ant/AbstractCatalinaTask.java
+++ b/java/org/apache/catalina/ant/AbstractCatalinaTask.java
@@ -27,9 +27,8 @@
 import java.net.URL;
 import java.net.URLConnection;
 
-import javax.xml.bind.DatatypeConverter;
-
 import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.codec.binary.Base64;
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
 
@@ -202,7 +201,7 @@
 
             // Set up an authorization header with our credentials
             String input = username + ":" + password;
-            String output = DatatypeConverter.printBase64Binary(
+            String output = Base64.encodeBase64String(
                     input.getBytes(B2CConverter.ISO_8859_1));
             hconn.setRequestProperty("Authorization",
                                      "Basic " + output);
diff --git a/java/org/apache/catalina/authenticator/BasicAuthenticator.java b/java/org/apache/catalina/authenticator/BasicAuthenticator.java
index bb7c4bb..5f49f89 100644
--- a/java/org/apache/catalina/authenticator/BasicAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/BasicAuthenticator.java
@@ -24,7 +24,6 @@
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.bind.DatatypeConverter;
 
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.deploy.LoginConfig;
@@ -33,6 +32,7 @@
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.codec.binary.Base64;
 
 
 
@@ -136,10 +136,10 @@
             if (authorizationBC.startsWithIgnoreCase("basic ", 0)) {
                 authorizationBC.setOffset(authorizationBC.getOffset() + 6);
                 
-                // Use the StringCache as these will be the same between
-                // requests
-                String encoded = authorizationBC.toStringInternal();
-                byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);
+                byte[] decoded = Base64.decodeBase64(
+                        authorizationBC.getBuffer(),
+                        authorizationBC.getOffset(),
+                        authorizationBC.getLength());
                 
                 // Get username and password
                 int colon = -1;
diff --git a/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java b/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
index 0ea4ca4..bc1e64e 100644
--- a/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/SpnegoAuthenticator.java
@@ -27,7 +27,6 @@
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.bind.DatatypeConverter;
 
 import org.apache.catalina.LifecycleException;
 import org.apache.catalina.connector.Request;
@@ -35,9 +34,9 @@
 import org.apache.catalina.startup.Bootstrap;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
-import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.codec.binary.Base64;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
@@ -192,13 +191,9 @@
 
         authorizationBC.setOffset(authorizationBC.getOffset() + 10);
                 
-        // Create the String directly as this will change on each request and we
-        // don't want to use the StringCache
-        String encoded = new String(authorizationBC.getBuffer(),
+        byte[] decoded = Base64.decodeBase64(authorizationBC.getBuffer(),
                 authorizationBC.getOffset(),
-                authorizationBC.getLength(), B2CConverter.ISO_8859_1);
-
-        byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);
+                authorizationBC.getLength());
 
         if (decoded.length == 0) {
             if (log.isDebugEnabled()) {
@@ -286,7 +281,7 @@
 
         // Send response token on success and failure
         response.setHeader("WWW-Authenticate", "Negotiate "
-                + DatatypeConverter.printBase64Binary(outToken));
+                + Base64.encodeBase64String(outToken));
 
         if (principal != null) {
             register(request, response, principal, Constants.SPNEGO_METHOD,
diff --git a/java/org/apache/catalina/realm/JNDIRealm.java b/java/org/apache/catalina/realm/JNDIRealm.java
index 543c0fa..213bee6 100644
--- a/java/org/apache/catalina/realm/JNDIRealm.java
+++ b/java/org/apache/catalina/realm/JNDIRealm.java
@@ -51,9 +51,10 @@
 import javax.naming.directory.InitialDirContext;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
-import javax.xml.bind.DatatypeConverter;
 
 import org.apache.catalina.LifecycleException;
+import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.codec.binary.Base64;
 import org.ietf.jgss.GSSCredential;
 
 /**
@@ -1572,8 +1573,9 @@
                     password = password.substring(5);
                     md.reset();
                     md.update(credentials.getBytes(Charset.defaultCharset()));
+                    byte[] decoded = Base64.decodeBase64(md.digest());
                     String digestedPassword =
-                            DatatypeConverter.printBase64Binary(md.digest());
+                            new String(decoded, B2CConverter.ISO_8859_1);
                     validated = password.equals(digestedPassword);
                 }
             } else if (password.startsWith("{SSHA}")) {
@@ -1586,17 +1588,15 @@
                     md.update(credentials.getBytes(Charset.defaultCharset()));
 
                     // Decode stored password.
-                    byte[] decoded =
-                            DatatypeConverter.parseBase64Binary(password);
+                    byte[] decoded = Base64.decodeBase64(password);
 
                     // Split decoded password into hash and salt.
                     final int saltpos = 20;
                     byte[] hash = new byte[saltpos];
                     System.arraycopy(decoded, 0, hash, 0, saltpos);
-                    byte[] salt = new byte[decoded.length - saltpos];
-                    System.arraycopy(decoded, saltpos, salt, 0, salt.length);
 
-                    md.update(salt);
+                    md.update(decoded, saltpos, decoded.length - saltpos);
+
                     byte[] dp = md.digest();
 
                     validated = Arrays.equals(dp, hash);
diff --git a/java/org/apache/catalina/util/Base64.java b/java/org/apache/catalina/util/Base64.java
index 39fdabc..b2468be 100644
--- a/java/org/apache/catalina/util/Base64.java
+++ b/java/org/apache/catalina/util/Base64.java
@@ -32,8 +32,7 @@
  * @author Jeffrey Rodriguez
  * @version $Id$
  *
- * @deprecated  Use {@link
- *              javax.xml.bind.DatatypeConverter#parseBase64Binary(String)}.
+ * @deprecated  Use {@link org.apache.tomcat.util.codec.binary.Base64}
  *              This class will be removed in Tomcat 8.
  */
 @Deprecated
diff --git a/java/org/apache/tomcat/util/codec/BinaryDecoder.java b/java/org/apache/tomcat/util/codec/BinaryDecoder.java
new file mode 100644
index 0000000..6be5209
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/BinaryDecoder.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Defines common decoding methods for byte array decoders.
+ *
+ * @version $Id$
+ */
+public interface BinaryDecoder extends Decoder {
+
+    /**
+     * Decodes a byte array and returns the results as a byte array.
+     *
+     * @param source
+     *            A byte array which has been encoded with the appropriate encoder
+     * @return a byte array that contains decoded content
+     * @throws DecoderException
+     *             A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
+     */
+    byte[] decode(byte[] source) throws DecoderException;
+}
+
diff --git a/java/org/apache/tomcat/util/codec/BinaryEncoder.java b/java/org/apache/tomcat/util/codec/BinaryEncoder.java
new file mode 100644
index 0000000..a8187b2
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/BinaryEncoder.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Defines common encoding methods for byte array encoders.
+ *
+ * @version $Id$
+ */
+public interface BinaryEncoder extends Encoder {
+
+    /**
+     * Encodes a byte array and return the encoded data as a byte array.
+     *
+     * @param source
+     *            Data to be encoded
+     * @return A byte array containing the encoded data
+     * @throws EncoderException
+     *             thrown if the Encoder encounters a failure condition during the encoding process.
+     */
+    byte[] encode(byte[] source) throws EncoderException;
+}
+
diff --git a/java/org/apache/tomcat/util/codec/Decoder.java b/java/org/apache/tomcat/util/codec/Decoder.java
new file mode 100644
index 0000000..ebf2efc
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/Decoder.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Provides the highest level of abstraction for Decoders.
+ * <p>
+ * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface.
+ * Allows a user to pass a generic Object to any Decoder implementation in the codec package.
+ * <p>
+ * One of the two interfaces at the center of the codec package.
+ *
+ * @version $Id$
+ */
+public interface Decoder {
+
+    /**
+     * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will
+     * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a
+     * {@link ClassCastException} occurs this decode method will throw a DecoderException.
+     *
+     * @param source
+     *            the object to decode
+     * @return a 'decoded" object
+     * @throws DecoderException
+     *             a decoder exception can be thrown for any number of reasons. Some good candidates are that the
+     *             parameter passed to this method is null, a param cannot be cast to the appropriate type for a
+     *             specific encoder.
+     */
+    Object decode(Object source) throws DecoderException;
+}
+
diff --git a/java/org/apache/tomcat/util/codec/DecoderException.java b/java/org/apache/tomcat/util/codec/DecoderException.java
new file mode 100644
index 0000000..39d789c
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/DecoderException.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder}
+ * encounters a decoding specific exception such as invalid data, or characters outside of the expected range.
+ *
+ * @version $Id$
+ */
+public class DecoderException extends Exception {
+
+    /**
+     * Declares the Serial Version Uid.
+     *
+     * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause}.
+     *
+     * @since 1.4
+     */
+    public DecoderException() {
+        super();
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+     * be initialized by a call to {@link #initCause}.
+     *
+     * @param message
+     *            The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+     */
+    public DecoderException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and cause.
+     * <p>
+     * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
+     * exception's detail message.
+     *
+     * @param message
+     *            The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+     * @param cause
+     *            The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+     *            value is permitted, and indicates that the cause is nonexistent or unknown.
+     * @since 1.4
+     */
+    public DecoderException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
+     * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
+     * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+     *
+     * @param cause
+     *            The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+     *            value is permitted, and indicates that the cause is nonexistent or unknown.
+     * @since 1.4
+     */
+    public DecoderException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/java/org/apache/tomcat/util/codec/Encoder.java b/java/org/apache/tomcat/util/codec/Encoder.java
new file mode 100644
index 0000000..1d16436
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/Encoder.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Provides the highest level of abstraction for Encoders.
+ * <p>
+ * This is the sister interface of {@link Decoder}.  Every implementation of Encoder provides this
+ * common generic interface which allows a user to pass a generic Object to any Encoder implementation
+ * in the codec package.
+ *
+ * @version $Id$
+ */
+public interface Encoder {
+
+    /**
+     * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be
+     * <code>byte[]</code> or <code>String</code>s depending on the implementation used.
+     *
+     * @param source
+     *            An object to encode
+     * @return An "encoded" Object
+     * @throws EncoderException
+     *             An encoder exception is thrown if the encoder experiences a failure condition during the encoding
+     *             process.
+     */
+    Object encode(Object source) throws EncoderException;
+}
+
diff --git a/java/org/apache/tomcat/util/codec/EncoderException.java b/java/org/apache/tomcat/util/codec/EncoderException.java
new file mode 100644
index 0000000..dadbcf7
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/EncoderException.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec;
+
+/**
+ * Thrown when there is a failure condition during the encoding process. This exception is thrown when an
+ * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum,
+ * characters outside of the expected range.
+ *
+ * @version $Id$
+ */
+public class EncoderException extends Exception {
+
+    /**
+     * Declares the Serial Version Uid.
+     *
+     * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may
+     * subsequently be initialized by a call to {@link #initCause}.
+     *
+     * @since 1.4
+     */
+    public EncoderException() {
+        super();
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+     * be initialized by a call to {@link #initCause}.
+     *
+     * @param message
+     *            a useful message relating to the encoder specific error.
+     */
+    public EncoderException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a new exception with the specified detail message and cause.
+     *
+     * <p>
+     * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
+     * exception's detail message.
+     * </p>
+     *
+     * @param message
+     *            The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+     * @param cause
+     *            The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+     *            value is permitted, and indicates that the cause is nonexistent or unknown.
+     * @since 1.4
+     */
+    public EncoderException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
+     * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
+     * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+     *
+     * @param cause
+     *            The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+     *            value is permitted, and indicates that the cause is nonexistent or unknown.
+     * @since 1.4
+     */
+    public EncoderException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/java/org/apache/tomcat/util/codec/binary/Base64.java b/java/org/apache/tomcat/util/codec/binary/Base64.java
new file mode 100644
index 0000000..b1c1f8a
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/binary/Base64.java
@@ -0,0 +1,779 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec.binary;
+
+import java.math.BigInteger;
+
+/**
+ * Provides Base64 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
+ *
+ * <p>
+ * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
+ * </p>
+ * <p>
+ * The class can be parameterized in the following manner with various constructors:
+ * <ul>
+ * <li>URL-safe mode: Default off.</li>
+ * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of
+ * 4 in the encoded data.
+ * <li>Line separator: Default is CRLF ("\r\n")</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only
+ * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252,
+ * UTF-8, etc).
+ * </p>
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @since 1.0
+ * @version $Id$
+ */
+public class Base64 extends BaseNCodec {
+
+    /**
+     * BASE32 characters are 6 bits in length.
+     * They are formed by taking a block of 3 octets to form a 24-bit string,
+     * which is converted into 4 BASE64 characters.
+     */
+    private static final int BITS_PER_ENCODED_BYTE = 6;
+    private static final int BYTES_PER_UNENCODED_BLOCK = 3;
+    private static final int BYTES_PER_ENCODED_BLOCK = 4;
+
+    /**
+     * Chunk separator per RFC 2045 section 2.1.
+     *
+     * <p>
+     * N.B. The next major release may break compatibility and make this field private.
+     * </p>
+     *
+     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
+     */
+    static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
+
+    /**
+     * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet"
+     * equivalents as specified in Table 1 of RFC 2045.
+     *
+     * Thanks to "commons" project in ws.apache.org for this code.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     */
+    private static final byte[] STANDARD_ENCODE_TABLE = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+    };
+
+    /**
+     * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
+     * changed to - and _ to make the encoded Base64 results more URL-SAFE.
+     * This table is only used when the Base64's mode is set to URL-SAFE.
+     */
+    private static final byte[] URL_SAFE_ENCODE_TABLE = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
+    };
+
+    /**
+     * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified
+     * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
+     * alphabet but fall within the bounds of the array are translated to -1.
+     *
+     * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
+     * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit).
+     *
+     * Thanks to "commons" project in ws.apache.org for this code.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     */
+    private static final byte[] DECODE_TABLE = {
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54,
+            55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
+            5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+            24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+            35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+    };
+
+    /**
+     * Base64 uses 6-bit fields.
+     */
+    /** Mask used to extract 6 bits, used when encoding */
+    private static final int MASK_6BITS = 0x3f;
+
+    // The static final fields above are used for the original static byte[] methods on Base64.
+    // The private member fields below are used with the new streaming approach, which requires
+    // some state be preserved between calls of encode() and decode().
+
+    /**
+     * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able
+     * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch
+     * between the two modes.
+     */
+    private final byte[] encodeTable;
+
+    // Only one decode table currently; keep for consistency with Base32 code
+    private final byte[] decodeTable = DECODE_TABLE;
+
+    /**
+     * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
+     */
+    private final byte[] lineSeparator;
+
+    /**
+     * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+     * <code>decodeSize = 3 + lineSeparator.length;</code>
+     */
+    private final int decodeSize;
+
+    /**
+     * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+     * <code>encodeSize = 4 + lineSeparator.length;</code>
+     */
+    private final int encodeSize;
+
+    /**
+     * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+     * <p>
+     * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE.
+     * </p>
+     *
+     * <p>
+     * When decoding all variants are supported.
+     * </p>
+     */
+    public Base64() {
+        this(0);
+    }
+
+    /**
+     * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode.
+     * <p>
+     * When encoding the line length is 76, the line separator is CRLF, and the encoding table is
+     * STANDARD_ENCODE_TABLE.
+     * </p>
+     *
+     * <p>
+     * When decoding all variants are supported.
+     * </p>
+     *
+     * @param urlSafe
+     *            if {@code true}, URL-safe encoding is used. In most cases this should be set to {@code false}.
+     * @since 1.4
+     */
+    public Base64(final boolean urlSafe) {
+        this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
+    }
+
+    /**
+     * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+     * <p>
+     * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is
+     * STANDARD_ENCODE_TABLE.
+     * </p>
+     * <p>
+     * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+     * </p>
+     * <p>
+     * When decoding all variants are supported.
+     * </p>
+     *
+     * @param lineLength
+     *            Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+     *            4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+     *            decoding.
+     * @since 1.4
+     */
+    public Base64(final int lineLength) {
+        this(lineLength, CHUNK_SEPARATOR);
+    }
+
+    /**
+     * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+     * <p>
+     * When encoding the line length and line separator are given in the constructor, and the encoding table is
+     * STANDARD_ENCODE_TABLE.
+     * </p>
+     * <p>
+     * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+     * </p>
+     * <p>
+     * When decoding all variants are supported.
+     * </p>
+     *
+     * @param lineLength
+     *            Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+     *            4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+     *            decoding.
+     * @param lineSeparator
+     *            Each line of encoded data will end with this sequence of bytes.
+     * @throws IllegalArgumentException
+     *             Thrown when the provided lineSeparator included some base64 characters.
+     * @since 1.4
+     */
+    public Base64(final int lineLength, final byte[] lineSeparator) {
+        this(lineLength, lineSeparator, false);
+    }
+
+    /**
+     * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+     * <p>
+     * When encoding the line length and line separator are given in the constructor, and the encoding table is
+     * STANDARD_ENCODE_TABLE.
+     * </p>
+     * <p>
+     * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+     * </p>
+     * <p>
+     * When decoding all variants are supported.
+     * </p>
+     *
+     * @param lineLength
+     *            Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+     *            4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+     *            decoding.
+     * @param lineSeparator
+     *            Each line of encoded data will end with this sequence of bytes.
+     * @param urlSafe
+     *            Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode
+     *            operations. Decoding seamlessly handles both modes.
+     *            <b>Note: no padding is added when using the URL-safe alphabet.</b>
+     * @throws IllegalArgumentException
+     *             The provided lineSeparator included some base64 characters. That's not going to work!
+     * @since 1.4
+     */
+    public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) {
+        super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
+                lineLength,
+                lineSeparator == null ? 0 : lineSeparator.length);
+        // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
+        // @see test case Base64Test.testConstructors()
+        if (lineSeparator != null) {
+            if (containsAlphabetOrPad(lineSeparator)) {
+                final String sep = StringUtils.newStringUtf8(lineSeparator);
+                throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
+            }
+            if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE
+                this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
+                this.lineSeparator = new byte[lineSeparator.length];
+                System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
+            } else {
+                this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+                this.lineSeparator = null;
+            }
+        } else {
+            this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+            this.lineSeparator = null;
+        }
+        this.decodeSize = this.encodeSize - 1;
+        this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
+    }
+
+    /**
+     * Returns our current encode mode. True if we're URL-SAFE, false otherwise.
+     *
+     * @return true if we're in URL-SAFE mode, false otherwise.
+     * @since 1.4
+     */
+    public boolean isUrlSafe() {
+        return this.encodeTable == URL_SAFE_ENCODE_TABLE;
+    }
+
+    /**
+     * <p>
+     * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+     * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last
+     * remaining bytes (if not multiple of 3).
+     * </p>
+     * <p><b>Note: no padding is added when encoding using the URL-safe alphabet.</b></p>
+     * <p>
+     * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     * </p>
+     *
+     * @param in
+     *            byte[] array of binary data to base64 encode.
+     * @param inPos
+     *            Position to start reading data from.
+     * @param inAvail
+     *            Amount of bytes available from input for encoding.
+     * @param context
+     *            the context to be used
+     */
+    @Override
+    void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
+        if (context.eof) {
+            return;
+        }
+        // inAvail < 0 is how we're informed of EOF in the underlying data we're
+        // encoding.
+        if (inAvail < 0) {
+            context.eof = true;
+            if (0 == context.modulus && lineLength == 0) {
+                return; // no leftovers to process and not using chunking
+            }
+            final byte[] buffer = ensureBufferSize(encodeSize, context);
+            final int savedPos = context.pos;
+            switch (context.modulus) { // 0-2
+                case 0 : // nothing to do here
+                    break;
+                case 1 : // 8 bits = 6 + 2
+                    // top 6 bits:
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS];
+                    // remaining 2:
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
+                    // URL-SAFE skips the padding to further reduce size.
+                    if (encodeTable == STANDARD_ENCODE_TABLE) {
+                        buffer[context.pos++] = PAD;
+                        buffer[context.pos++] = PAD;
+                    }
+                    break;
+
+                case 2 : // 16 bits = 6 + 6 + 4
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
+                    // URL-SAFE skips the padding to further reduce size.
+                    if (encodeTable == STANDARD_ENCODE_TABLE) {
+                        buffer[context.pos++] = PAD;
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException("Impossible modulus "+context.modulus);
+            }
+            context.currentLinePos += context.pos - savedPos; // keep track of current line position
+            // if currentPos == 0 we are at the start of a line, so don't add CRLF
+            if (lineLength > 0 && context.currentLinePos > 0) {
+                System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+                context.pos += lineSeparator.length;
+            }
+        } else {
+            for (int i = 0; i < inAvail; i++) {
+                final byte[] buffer = ensureBufferSize(encodeSize, context);
+                context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
+                int b = in[inPos++];
+                if (b < 0) {
+                    b += 256;
+                }
+                context.ibitWorkArea = (context.ibitWorkArea << 8) + b; //  BITS_PER_BYTE
+                if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
+                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
+                    buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
+                    context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
+                    if (lineLength > 0 && lineLength <= context.currentLinePos) {
+                        System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+                        context.pos += lineSeparator.length;
+                        context.currentLinePos = 0;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+     * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+     * call is not necessary when decoding, but it doesn't hurt, either.
+     * </p>
+     * <p>
+     * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+     * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+     * garbage-out philosophy: it will not check the provided data for validity.
+     * </p>
+     * <p>
+     * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+     * </p>
+     *
+     * @param in
+     *            byte[] array of ascii data to base64 decode.
+     * @param inPos
+     *            Position to start reading data from.
+     * @param inAvail
+     *            Amount of bytes available from input for encoding.
+     * @param context
+     *            the context to be used
+     */
+    @Override
+    void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
+        if (context.eof) {
+            return;
+        }
+        if (inAvail < 0) {
+            context.eof = true;
+        }
+        for (int i = 0; i < inAvail; i++) {
+            final byte[] buffer = ensureBufferSize(decodeSize, context);
+            final byte b = in[inPos++];
+            if (b == PAD) {
+                // We're done.
+                context.eof = true;
+                break;
+            } else {
+                if (b >= 0 && b < DECODE_TABLE.length) {
+                    final int result = DECODE_TABLE[b];
+                    if (result >= 0) {
+                        context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
+                        context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
+                        if (context.modulus == 0) {
+                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
+                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+                            buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Two forms of EOF as far as base64 decoder is concerned: actual
+        // EOF (-1) and first time '=' character is encountered in stream.
+        // This approach makes the '=' padding characters completely optional.
+        if (context.eof && context.modulus != 0) {
+            final byte[] buffer = ensureBufferSize(decodeSize, context);
+
+            // We have some spare bits remaining
+            // Output all whole multiples of 8 bits and ignore the rest
+            switch (context.modulus) {
+//              case 0 : // impossible, as excluded above
+                case 1 : // 6 bits - ignore entirely
+                    // TODO not currently tested; perhaps it is impossible?
+                    break;
+                case 2 : // 12 bits = 8 + 4
+                    context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
+                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+                    break;
+                case 3 : // 18 bits = 8 + 8 + 2
+                    context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
+                    buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+                    break;
+                default:
+                    throw new IllegalStateException("Impossible modulus "+context.modulus);
+            }
+        }
+    }
+
+    /**
+     * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+     * method treats whitespace as valid.
+     *
+     * @param arrayOctet
+     *            byte array to test
+     * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+     *         {@code false}, otherwise
+     * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0.
+     */
+    @Deprecated
+    public static boolean isArrayByteBase64(final byte[] arrayOctet) {
+        return isBase64(arrayOctet);
+    }
+
+    /**
+     * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
+     *
+     * @param octet
+     *            The value to test
+     * @return {@code true} if the value is defined in the the base 64 alphabet, {@code false} otherwise.
+     * @since 1.4
+     */
+    public static boolean isBase64(final byte octet) {
+        return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
+    }
+
+    /**
+     * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the
+     * method treats whitespace as valid.
+     *
+     * @param base64
+     *            String to test
+     * @return {@code true} if all characters in the String are valid characters in the Base64 alphabet or if
+     *         the String is empty; {@code false}, otherwise
+     *  @since 1.5
+     */
+    public static boolean isBase64(final String base64) {
+        return isBase64(StringUtils.getBytesUtf8(base64));
+    }
+
+    /**
+     * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+     * method treats whitespace as valid.
+     *
+     * @param arrayOctet
+     *            byte array to test
+     * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+     *         {@code false}, otherwise
+     * @since 1.5
+     */
+    public static boolean isBase64(final byte[] arrayOctet) {
+        for (int i = 0; i < arrayOctet.length; i++) {
+            if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm but does not chunk the output.
+     *
+     * @param binaryData
+     *            binary data to encode
+     * @return byte[] containing Base64 characters in their UTF-8 representation.
+     */
+    public static byte[] encodeBase64(final byte[] binaryData) {
+        return encodeBase64(binaryData, false);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm but does not chunk the output.
+     *
+     * NOTE:  We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to
+     * single-line non-chunking (commons-codec-1.5).
+     *
+     * @param binaryData
+     *            binary data to encode
+     * @return String containing Base64 characters.
+     * @since 1.4 (NOTE:  1.4 chunked the output, whereas 1.5 does not).
+     */
+    public static String encodeBase64String(final byte[] binaryData) {
+        return StringUtils.newStringUtf8(encodeBase64(binaryData, false));
+    }
+
+    /**
+     * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+     * url-safe variation emits - and _ instead of + and / characters.
+     * <b>Note: no padding is added.</b>
+     * @param binaryData
+     *            binary data to encode
+     * @return byte[] containing Base64 characters in their UTF-8 representation.
+     * @since 1.4
+     */
+    public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
+        return encodeBase64(binaryData, false, true);
+    }
+
+    /**
+     * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+     * url-safe variation emits - and _ instead of + and / characters.
+     * <b>Note: no padding is added.</b>
+     * @param binaryData
+     *            binary data to encode
+     * @return String containing Base64 characters
+     * @since 1.4
+     */
+    public static String encodeBase64URLSafeString(final byte[] binaryData) {
+        return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true));
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
+     *
+     * @param binaryData
+     *            binary data to encode
+     * @return Base64 characters chunked in 76 character blocks
+     */
+    public static byte[] encodeBase64Chunked(final byte[] binaryData) {
+        return encodeBase64(binaryData, true);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+     *
+     * @param binaryData
+     *            Array containing binary data to encode.
+     * @param isChunked
+     *            if {@code true} this encoder will chunk the base64 output into 76 character blocks
+     * @return Base64-encoded data.
+     * @throws IllegalArgumentException
+     *             Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+     */
+    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) {
+        return encodeBase64(binaryData, isChunked, false);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+     *
+     * @param binaryData
+     *            Array containing binary data to encode.
+     * @param isChunked
+     *            if {@code true} this encoder will chunk the base64 output into 76 character blocks
+     * @param urlSafe
+     *            if {@code true} this encoder will emit - and _ instead of the usual + and / characters.
+     *            <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
+     * @return Base64-encoded data.
+     * @throws IllegalArgumentException
+     *             Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+     * @since 1.4
+     */
+    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) {
+        return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+     *
+     * @param binaryData
+     *            Array containing binary data to encode.
+     * @param isChunked
+     *            if {@code true} this encoder will chunk the base64 output into 76 character blocks
+     * @param urlSafe
+     *            if {@code true} this encoder will emit - and _ instead of the usual + and / characters.
+     *            <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
+     * @param maxResultSize
+     *            The maximum result size to accept.
+     * @return Base64-encoded data.
+     * @throws IllegalArgumentException
+     *             Thrown when the input array needs an output array bigger than maxResultSize
+     * @since 1.4
+     */
+    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked,
+                                      final boolean urlSafe, final int maxResultSize) {
+        if (binaryData == null || binaryData.length == 0) {
+            return binaryData;
+        }
+
+        // Create this so can use the super-class method
+        // Also ensures that the same roundings are performed by the ctor and the code
+        final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
+        final long len = b64.getEncodedLength(binaryData);
+        if (len > maxResultSize) {
+            throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
+                len +
+                ") than the specified maximum size of " +
+                maxResultSize);
+        }
+
+        return b64.encode(binaryData);
+    }
+
+    /**
+     * Decodes a Base64 String into octets
+     *
+     * @param base64String
+     *            String containing Base64 data
+     * @return Array containing decoded data.
+     * @since 1.4
+     */
+    public static byte[] decodeBase64(final String base64String) {
+        return new Base64().decode(base64String);
+    }
+
+    /**
+     * Decodes Base64 data into octets
+     *
+     * @param base64Data
+     *            Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    public static byte[] decodeBase64(final byte[] base64Data) {
+        return decodeBase64(base64Data, 0, base64Data.length);
+    }
+
+    public  static byte[] decodeBase64(
+            final byte[] base64Data, final int off, final int len) {
+        return new Base64().decode(base64Data, off, len);
+    }
+
+    // Implementation of the Encoder Interface
+
+    // Implementation of integer encoding used for crypto
+    /**
+     * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature
+     *
+     * @param pArray
+     *            a byte array containing base64 character data
+     * @return A BigInteger
+     * @since 1.4
+     */
+    public static BigInteger decodeInteger(final byte[] pArray) {
+        return new BigInteger(1, decodeBase64(pArray));
+    }
+
+    /**
+     * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature
+     *
+     * @param bigInt
+     *            a BigInteger
+     * @return A byte array containing base64 character data
+     * @throws NullPointerException
+     *             if null is passed in
+     * @since 1.4
+     */
+    public static byte[] encodeInteger(final BigInteger bigInt) {
+        if (bigInt == null) {
+            throw new NullPointerException("encodeInteger called with null parameter");
+        }
+        return encodeBase64(toIntegerBytes(bigInt), false);
+    }
+
+    /**
+     * Returns a byte-array representation of a <code>BigInteger</code> without sign bit.
+     *
+     * @param bigInt
+     *            <code>BigInteger</code> to be converted
+     * @return a byte array representation of the BigInteger parameter
+     */
+    static byte[] toIntegerBytes(final BigInteger bigInt) {
+        int bitlen = bigInt.bitLength();
+        // round bitlen
+        bitlen = ((bitlen + 7) >> 3) << 3;
+        final byte[] bigBytes = bigInt.toByteArray();
+
+        if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
+            return bigBytes;
+        }
+        // set up params for copying everything but sign bit
+        int startSrc = 0;
+        int len = bigBytes.length;
+
+        // if bigInt is exactly byte-aligned, just skip signbit in copy
+        if ((bigInt.bitLength() % 8) == 0) {
+            startSrc = 1;
+            len--;
+        }
+        final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
+        final byte[] resizedBytes = new byte[bitlen / 8];
+        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
+        return resizedBytes;
+    }
+
+    /**
+     * Returns whether or not the <code>octet</code> is in the Base64 alphabet.
+     *
+     * @param octet
+     *            The value to test
+     * @return {@code true} if the value is defined in the the Base64 alphabet {@code false} otherwise.
+     */
+    @Override
+    protected boolean isInAlphabet(final byte octet) {
+        return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
new file mode 100644
index 0000000..9f38342
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
@@ -0,0 +1,503 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec.binary;
+
+import org.apache.tomcat.util.codec.BinaryDecoder;
+import org.apache.tomcat.util.codec.BinaryEncoder;
+import org.apache.tomcat.util.codec.DecoderException;
+import org.apache.tomcat.util.codec.EncoderException;
+
+/**
+ * Abstract superclass for Base-N encoders and decoders.
+ *
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @version $Id$
+ */
+public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder {
+
+    /**
+     * Holds thread context so classes can be thread-safe.
+     *
+     * This class is not itself thread-safe; each thread must allocate its own copy.
+     *
+     * @since 1.7
+     */
+    static class Context {
+
+        /**
+         * Place holder for the bytes we're dealing with for our based logic.
+         * Bitwise operations store and extract the encoding or decoding from this variable.
+         */
+        int ibitWorkArea;
+
+        /**
+         * Place holder for the bytes we're dealing with for our based logic.
+         * Bitwise operations store and extract the encoding or decoding from this variable.
+         */
+        long lbitWorkArea;
+
+        /**
+         * Buffer for streaming.
+         */
+        byte[] buffer;
+
+        /**
+         * Position where next character should be written in the buffer.
+         */
+        int pos;
+
+        /**
+         * Position where next character should be read from the buffer.
+         */
+        int readPos;
+
+        /**
+         * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+         * and must be thrown away.
+         */
+        boolean eof;
+
+        /**
+         * Variable tracks how many characters have been written to the current line. Only used when encoding. We use
+         * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0).
+         */
+        int currentLinePos;
+
+        /**
+         * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This
+         * variable helps track that.
+         */
+        int modulus;
+
+        Context() {
+        }
+
+        /**
+         * Returns a String useful for debugging (especially within a debugger.)
+         *
+         * @return a String useful for debugging.
+         */
+        @SuppressWarnings("boxing") // OK to ignore boxing here
+        @Override
+        public String toString() {
+            return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " +
+                    "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), buffer, currentLinePos, eof,
+                    ibitWorkArea, lbitWorkArea, modulus, pos, readPos);
+        }
+    }
+
+    /**
+     * EOF
+     *
+     * @since 1.7
+     */
+    static final int EOF = -1;
+
+    /**
+     *  MIME chunk size per RFC 2045 section 6.8.
+     *
+     * <p>
+     * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+     * equal signs.
+     * </p>
+     *
+     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
+     */
+    public static final int MIME_CHUNK_SIZE = 76;
+
+    /**
+     * PEM chunk size per RFC 1421 section 4.3.2.4.
+     *
+     * <p>
+     * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+     * equal signs.
+     * </p>
+     *
+     * @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section 4.3.2.4</a>
+     */
+    public static final int PEM_CHUNK_SIZE = 64;
+
+    private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+
+    /**
+     * Defines the default buffer size - currently {@value}
+     * - must be large enough for at least one encoded block+separator
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    /** Mask used to extract 8 bits, used in decoding bytes */
+    protected static final int MASK_8BITS = 0xff;
+
+    /**
+     * Byte used to pad output.
+     */
+    protected static final byte PAD_DEFAULT = '='; // Allow static access to default
+
+    protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later
+
+    /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
+    private final int unencodedBlockSize;
+
+    /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
+    private final int encodedBlockSize;
+
+    /**
+     * Chunksize for encoding. Not used when decoding.
+     * A value of zero or less implies no chunking of the encoded data.
+     * Rounded down to nearest multiple of encodedBlockSize.
+     */
+    protected final int lineLength;
+
+    /**
+     * Size of chunk separator. Not used unless {@link #lineLength} > 0.
+     */
+    private final int chunkSeparatorLength;
+
+    /**
+     * Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize}
+     * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
+     * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
+     * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
+     * @param lineLength if &gt; 0, use chunking with a length <code>lineLength</code>
+     * @param chunkSeparatorLength the chunk separator length, if relevant
+     */
+    protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
+                         final int lineLength, final int chunkSeparatorLength) {
+        this.unencodedBlockSize = unencodedBlockSize;
+        this.encodedBlockSize = encodedBlockSize;
+        final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
+        this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
+        this.chunkSeparatorLength = chunkSeparatorLength;
+    }
+
+    /**
+     * Returns true if this object has buffered data for reading.
+     *
+     * @param context the context to be used
+     * @return true if there is data still available for reading.
+     */
+    boolean hasData(final Context context) {  // package protected for access from I/O streams
+        return context.buffer != null;
+    }
+
+    /**
+     * Returns the amount of buffered data available for reading.
+     *
+     * @param context the context to be used
+     * @return The amount of buffered data available for reading.
+     */
+    int available(final Context context) {  // package protected for access from I/O streams
+        return context.buffer != null ? context.pos - context.readPos : 0;
+    }
+
+    /**
+     * Get the default buffer size. Can be overridden.
+     *
+     * @return {@link #DEFAULT_BUFFER_SIZE}
+     */
+    protected int getDefaultBufferSize() {
+        return DEFAULT_BUFFER_SIZE;
+    }
+
+    /**
+     * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
+     * @param context the context to be used
+     */
+    private byte[] resizeBuffer(final Context context) {
+        if (context.buffer == null) {
+            context.buffer = new byte[getDefaultBufferSize()];
+            context.pos = 0;
+            context.readPos = 0;
+        } else {
+            final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+            System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
+            context.buffer = b;
+        }
+        return context.buffer;
+    }
+
+    /**
+     * Ensure that the buffer has room for <code>size</code> bytes
+     *
+     * @param size minimum spare space required
+     * @param context the context to be used
+     */
+    protected byte[] ensureBufferSize(final int size, final Context context){
+        if ((context.buffer == null) || (context.buffer.length < context.pos + size)){
+            return resizeBuffer(context);
+        }
+        return context.buffer;
+    }
+
+    /**
+     * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail
+     * bytes. Returns how many bytes were actually extracted.
+     * <p>
+     * Package protected for access from I/O streams.
+     *
+     * @param b
+     *            byte[] array to extract the buffered data into.
+     * @param bPos
+     *            position in byte[] array to start extraction at.
+     * @param bAvail
+     *            amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
+     * @param context
+     *            the context to be used
+     * @return The number of bytes successfully extracted into the provided byte[] array.
+     */
+    int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) {
+        if (context.buffer != null) {
+            final int len = Math.min(available(context), bAvail);
+            System.arraycopy(context.buffer, context.readPos, b, bPos, len);
+            context.readPos += len;
+            if (context.readPos >= context.pos) {
+                context.buffer = null; // so hasData() will return false, and this method can return -1
+            }
+            return len;
+        }
+        return context.eof ? EOF : 0;
+    }
+
+    /**
+     * Checks if a byte value is whitespace or not.
+     * Whitespace is taken to mean: space, tab, CR, LF
+     * @param byteToCheck
+     *            the byte to check
+     * @return true if byte is whitespace, false otherwise
+     */
+    protected static boolean isWhiteSpace(final byte byteToCheck) {
+        switch (byteToCheck) {
+            case ' ' :
+            case '\n' :
+            case '\r' :
+            case '\t' :
+                return true;
+            default :
+                return false;
+        }
+    }
+
+    /**
+     * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+     * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
+     *
+     * @param obj
+     *            Object to encode
+     * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
+     * @throws EncoderException
+     *             if the parameter supplied is not of type byte[]
+     */
+    @Override
+    public Object encode(final Object obj) throws EncoderException {
+        if (!(obj instanceof byte[])) {
+            throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]");
+        }
+        return encode((byte[]) obj);
+    }
+
+    /**
+     * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
+     * Uses UTF8 encoding.
+     *
+     * @param pArray
+     *            a byte array containing binary data
+     * @return A String containing only Base-N character data
+     */
+    public String encodeToString(final byte[] pArray) {
+        return StringUtils.newStringUtf8(encode(pArray));
+    }
+
+    /**
+     * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
+     * Uses UTF8 encoding.
+     *
+     * @param pArray a byte array containing binary data
+     * @return String containing only character data in the appropriate alphabet.
+    */
+    public String encodeAsString(final byte[] pArray){
+        return StringUtils.newStringUtf8(encode(pArray));
+    }
+
+    /**
+     * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+     * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
+     *
+     * @param obj
+     *            Object to decode
+     * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String
+     *         supplied.
+     * @throws DecoderException
+     *             if the parameter supplied is not of type byte[]
+     */
+    @Override
+    public Object decode(final Object obj) throws DecoderException {
+        if (obj instanceof byte[]) {
+            return decode((byte[]) obj);
+        } else if (obj instanceof String) {
+            return decode((String) obj);
+        } else {
+            throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String");
+        }
+    }
+
+    /**
+     * Decodes a String containing characters in the Base-N alphabet.
+     *
+     * @param pArray
+     *            A String containing Base-N character data
+     * @return a byte array containing binary data
+     */
+    public byte[] decode(final String pArray) {
+        return decode(StringUtils.getBytesUtf8(pArray));
+    }
+
+    /**
+     * Decodes a byte[] containing characters in the Base-N alphabet.
+     *
+     * @param pArray
+     *            A byte array containing Base-N character data
+     * @return a byte array containing binary data
+     */
+    @Override
+    public byte[] decode(final byte[] pArray) {
+        return decode(pArray, 0, pArray.length);
+    }
+
+    public byte[] decode(final byte[] pArray, final int off, final int len) {
+        if (pArray == null || len == 0) {
+            return new byte[0];
+        }
+        final Context context = new Context();
+        decode(pArray, off, len, context);
+        decode(pArray, off, EOF, context); // Notify decoder of EOF.
+        final byte[] result = new byte[context.pos];
+        readResults(result, 0, result.length, context);
+        return result;
+    }
+
+    /**
+     * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+     *
+     * @param pArray
+     *            a byte array containing binary data
+     * @return A byte array containing only the basen alphabetic character data
+     */
+    @Override
+    public byte[] encode(final byte[] pArray) {
+        if (pArray == null || pArray.length == 0) {
+            return pArray;
+        }
+        final Context context = new Context();
+        encode(pArray, 0, pArray.length, context);
+        encode(pArray, 0, EOF, context); // Notify encoder of EOF.
+        final byte[] buf = new byte[context.pos - context.readPos];
+        readResults(buf, 0, buf.length, context);
+        return buf;
+    }
+
+    // package protected for access from I/O streams
+    abstract void encode(byte[] pArray, int i, int length, Context context);
+
+    // package protected for access from I/O streams
+    abstract void decode(byte[] pArray, int i, int length, Context context);
+
+    /**
+     * Returns whether or not the <code>octet</code> is in the current alphabet.
+     * Does not allow whitespace or pad.
+     *
+     * @param value The value to test
+     *
+     * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise.
+     */
+    protected abstract boolean isInAlphabet(byte value);
+
+    /**
+     * Tests a given byte array to see if it contains only valid characters within the alphabet.
+     * The method optionally treats whitespace and pad as valid.
+     *
+     * @param arrayOctet byte array to test
+     * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed
+     *
+     * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty;
+     *         {@code false}, otherwise
+     */
+    public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
+        for (int i = 0; i < arrayOctet.length; i++) {
+            if (!isInAlphabet(arrayOctet[i]) &&
+                    (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Tests a given String to see if it contains only valid characters within the alphabet.
+     * The method treats whitespace and PAD as valid.
+     *
+     * @param basen String to test
+     * @return {@code true} if all characters in the String are valid characters in the alphabet or if
+     *         the String is empty; {@code false}, otherwise
+     * @see #isInAlphabet(byte[], boolean)
+     */
+    public boolean isInAlphabet(final String basen) {
+        return isInAlphabet(StringUtils.getBytesUtf8(basen), true);
+    }
+
+    /**
+     * Tests a given byte array to see if it contains any characters within the alphabet or PAD.
+     *
+     * Intended for use in checking line-ending arrays
+     *
+     * @param arrayOctet
+     *            byte array to test
+     * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise
+     */
+    protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
+        if (arrayOctet == null) {
+            return false;
+        }
+        for (final byte element : arrayOctet) {
+            if (PAD == element || isInAlphabet(element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Calculates the amount of space needed to encode the supplied array.
+     *
+     * @param pArray byte[] array which will later be encoded
+     *
+     * @return amount of space needed to encoded the supplied array.
+     * Returns a long since a max-len array will require > Integer.MAX_VALUE
+     */
+    public long getEncodedLength(final byte[] pArray) {
+        // Calculate non-chunked size - rounded up to allow for padding
+        // cast to long is needed to avoid possibility of overflow
+        long len = ((pArray.length + unencodedBlockSize-1)  / unencodedBlockSize) * (long) encodedBlockSize;
+        if (lineLength > 0) { // We're using chunking
+            // Round up to nearest multiple
+            len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
+        }
+        return len;
+    }
+}
diff --git a/java/org/apache/tomcat/util/codec/binary/StringUtils.java b/java/org/apache/tomcat/util/codec/binary/StringUtils.java
new file mode 100644
index 0000000..2ad44b2
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/binary/StringUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.apache.tomcat.util.codec.binary;
+
+import java.nio.charset.Charset;
+
+import org.apache.tomcat.util.buf.B2CConverter;
+
+/**
+ * Converts String to and from bytes using the encodings required by the Java specification. These encodings are
+ * specified in <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">
+ * Standard charsets</a>.
+ *
+ * <p>This class is immutable and thread-safe.</p>
+ *
+ * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
+ * @version $Id$
+ * @since 1.4
+ */
+public class StringUtils {
+
+    /**
+     * Calls {@link String#getBytes(Charset)}
+     *
+     * @param string
+     *            The string to encode (if null, return null).
+     * @param charset
+     *            The {@link Charset} to encode the {@code String}
+     * @return the encoded bytes
+     */
+    private static byte[] getBytes(final String string, final Charset charset) {
+        if (string == null) {
+            return null;
+        }
+        return string.getBytes(charset);
+    }
+
+    /**
+     * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte
+     * array.
+     *
+     * @param string
+     *            the String to encode, may be {@code null}
+     * @return encoded bytes, or {@code null} if the input string was {@code null}
+     * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
+     */
+    public static byte[] getBytesUtf8(final String string) {
+        return getBytes(string, B2CConverter.UTF_8);
+    }
+
+    /**
+     * Constructs a new <code>String</code> by decoding the specified array of bytes using the given charset.
+     *
+     * @param bytes
+     *            The bytes to be decoded into characters
+     * @param charset
+     *            The {@link Charset} to encode the {@code String}
+     * @return A new <code>String</code> decoded from the specified array of bytes using the given charset,
+     *         or {@code null} if the input byte array was {@code null}.
+     */
+    private static String newString(final byte[] bytes, final Charset charset) {
+        return bytes == null ? null : new String(bytes, charset);
+    }
+
+    /**
+     * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-8 charset.
+     *
+     * @param bytes
+     *            The bytes to be decoded into characters
+     * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-8 charset,
+     *         or {@code null} if the input byte array was {@code null}.
+     */
+    public static String newStringUtf8(final byte[] bytes) {
+        return newString(bytes, B2CConverter.UTF_8);
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/codec/binary/package.html b/java/org/apache/tomcat/util/codec/binary/package.html
new file mode 100644
index 0000000..13345ec
--- /dev/null
+++ b/java/org/apache/tomcat/util/codec/binary/package.html
@@ -0,0 +1,21 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF 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.
+-->
+<html>
+ <body>
+  Base64, Base32, Binary, and Hexadecimal String encoding and decoding.
+ </body>
+</html>
diff --git a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
index 669e683..9d24063 100644
--- a/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
+++ b/java/org/apache/tomcat/util/http/fileupload/util/mime/MimeUtility.java
@@ -23,7 +23,7 @@
 import java.util.Locale;
 import java.util.Map;
 
-import javax.xml.bind.DatatypeConverter;
+import org.apache.tomcat.util.codec.binary.Base64;
 
 /**
  * Utility class to decode MIME texts.
@@ -245,7 +245,7 @@
             byte[] decodedData;
             // Base64 encoded?
             if (encoding.equals(BASE64_ENCODING_MARKER)) {
-                decodedData = DatatypeConverter.parseBase64Binary(encodedText);
+                decodedData = Base64.decodeBase64(encodedText);
             } else if (encoding.equals(QUOTEDPRINTABLE_ENCODING_MARKER)) { // maybe quoted printable.
                 byte[] encodedData = encodedText.getBytes(US_ASCII_CHARSET);
                 QuotedPrintableDecoder.decode(encodedData, out);
diff --git a/test/org/apache/catalina/authenticator/TestNonLoginAndBasicAuthenticator.java b/test/org/apache/catalina/authenticator/TestNonLoginAndBasicAuthenticator.java
index 830c515..1364e69 100644
--- a/test/org/apache/catalina/authenticator/TestNonLoginAndBasicAuthenticator.java
+++ b/test/org/apache/catalina/authenticator/TestNonLoginAndBasicAuthenticator.java
@@ -22,7 +22,6 @@
 import java.util.Map;
 
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.bind.DatatypeConverter;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -37,7 +36,9 @@
 import org.apache.catalina.startup.TesterServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.codec.binary.Base64;
 
 /**
  * Test BasicAuthenticator and NonLoginAuthenticator when a
@@ -613,9 +614,9 @@
             username = aUsername;
             password = aPassword;
             String userCredentials = username + ":" + password;
-            byte[] credentialsBytes = ByteChunk.convertToBytes(userCredentials);
-            String base64auth =
-                    DatatypeConverter.printBase64Binary(credentialsBytes);
+            byte[] credentialsBytes =
+                    userCredentials.getBytes(B2CConverter.ISO_8859_1);
+            String base64auth = Base64.encodeBase64String(credentialsBytes);
             credentials= method + " " + base64auth;
         }
 
diff --git a/test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java b/test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java
index b4fc747..cece327 100644
--- a/test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java
+++ b/test/org/apache/catalina/authenticator/TestSSOnonLoginAndBasicAuthenticator.java
@@ -21,8 +21,6 @@
 import java.util.List;
 import java.util.Map;
 
-import javax.xml.bind.DatatypeConverter;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -36,7 +34,9 @@
 import org.apache.catalina.startup.TesterServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.codec.binary.Base64;
 
 /**
  * Test BasicAuthenticator and NonLoginAuthenticator when a
@@ -237,11 +237,11 @@
             return;
         }
 
-        // the second access attempt should be sucessful
+        // the second access attempt should be successful
         String credentials = user + ":" + pwd;
-        byte[] credentialsBytes = ByteChunk.convertToBytes(credentials);
-        String base64auth =
-                DatatypeConverter.printBase64Binary(credentialsBytes);
+
+        String base64auth = Base64.encodeBase64String(
+                credentials.getBytes(B2CConverter.ISO_8859_1));
         String authLine = "Basic " + base64auth;
 
         List<String> auth = new ArrayList<String>();
diff --git a/test/org/apache/catalina/util/TesterBase64Performance.java b/test/org/apache/catalina/util/TesterBase64Performance.java
index c2b2ad9..4c8eb40 100644
--- a/test/org/apache/catalina/util/TesterBase64Performance.java
+++ b/test/org/apache/catalina/util/TesterBase64Performance.java
@@ -36,8 +36,6 @@
     public void testDecode() throws Exception {
 
         List<ByteChunk> inputs = new ArrayList<ByteChunk>(SIZE);
-        List<ByteChunk> warmups = new ArrayList<ByteChunk>(SIZE);
-        List<String> results = new ArrayList<String>(SIZE);
 
         for (int i = 0; i < SIZE; i++) {
             String decodedString = "abc" + Integer.valueOf(i) +
@@ -55,60 +53,20 @@
             inputs.add(bc);
         }
 
-
-        for (int i = 0; i < SIZE; i++) {
-            String decodedString = "zyx" + Integer.valueOf(i) +
-                    ":zyx" + Integer.valueOf(i);
-            byte[] decodedBytes =
-                    decodedString.getBytes(B2CConverter.ISO_8859_1);
-            String encodedString =
-                    DatatypeConverter.printBase64Binary(decodedBytes);
-            byte[] encodedBytes =
-                    encodedString.getBytes(B2CConverter.ISO_8859_1);
-
-            ByteChunk bc = new ByteChunk(encodedBytes.length);
-            bc.append(encodedBytes, 0, encodedBytes.length);
-
-            warmups.add(bc);
-        }
-
-        //Warm up
-        for (ByteChunk bc : warmups) {
-            CharChunk cc = new CharChunk(bc.getLength());
-            Base64.decode(bc, cc);
-            results.add(cc.toString());
-        }
-        results.clear();
-
-        for (ByteChunk bc : warmups) {
-            byte[] decodedBytes =
-                    DatatypeConverter.parseBase64Binary(bc.toString());
-            String decodedString =
-                    new String(decodedBytes, B2CConverter.ISO_8859_1);
-            results.add(decodedString);
-        }
-        results.clear();
-
         long startTomcat = System.currentTimeMillis();
         for (ByteChunk bc : inputs) {
             CharChunk cc = new CharChunk(bc.getLength());
             Base64.decode(bc, cc);
-            results.add(cc.toString());
         }
         long stopTomcat =  System.currentTimeMillis();
         System.out.println("Tomcat: " + (stopTomcat - startTomcat) + " ms");
 
-        results.clear();
-
-        long startJre = System.currentTimeMillis();
+        long startCodec = System.currentTimeMillis();
         for (ByteChunk bc : inputs) {
-            byte[] decodedBytes =
-                    DatatypeConverter.parseBase64Binary(bc.toString());
-            String decodedString =
-                    new String(decodedBytes, B2CConverter.ISO_8859_1);
-            results.add(decodedString);
+            org.apache.tomcat.util.codec.binary.Base64.decodeBase64(
+                    bc.getBuffer(), bc.getOffset(), bc.getLength());
         }
-        long stopJre =  System.currentTimeMillis();
-        System.out.println("JRE: " + (stopJre - startJre) + " ms");
+        long stopCodec =  System.currentTimeMillis();
+        System.out.println("Codec: " + (stopCodec - startCodec) + " ms");
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 5ca368f..a75acbf 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -98,7 +98,8 @@
       </fix>
       <scode>
         Deprecate Tomcat&apos;s internal Base 64 encoder/decoder and switch to
-        using the JRE provided implementation. (markt)
+        using a package renamed copy of the Commons Codec implementation.
+        (markt)
       </scode>
       <fix>
         Ensure that StandardJarScanner#scan will use the provided class loader