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 > 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'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