You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2022/05/28 13:52:30 UTC

[httpcomponents-client] branch master updated: HTTPCLIENT-2218: Use Java 8 Base64 utility (#370)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b0733ad5 HTTPCLIENT-2218: Use Java 8 Base64 utility (#370)
5b0733ad5 is described below

commit 5b0733ad5dfca583f852bbb456c8578d2203c1d8
Author: j3graham <j3...@gmail.com>
AuthorDate: Sat May 28 09:52:24 2022 -0400

    HTTPCLIENT-2218: Use Java 8 Base64 utility (#370)
---
 .../cache/memcached/SHA256KeyHashingScheme.java    |   2 +-
 .../testing/auth/BasicAuthTokenExtractor.java      |   8 +-
 .../http/impl/win/WindowsNegotiateScheme.java      |   2 +-
 httpclient5/pom.xml                                |   4 -
 .../http/entity/mime/HttpRFC7578Multipart.java     |  11 +-
 .../hc/client5/http/impl/auth/BasicScheme.java     |   2 +-
 .../hc/client5/http/impl/auth/GGSSchemeBase.java   |   2 +-
 .../hc/client5/http/impl/auth/NTLMEngineImpl.java  |   2 +-
 .../org/apache/hc/client5/http/utils/Base64.java   | 164 +++++++++++++++++++++
 .../java/org/apache/hc/client5/http/utils/Hex.java |  94 ++++++++++++
 .../http/entity/mime/HttpRFC7578MultipartTest.java |   3 +-
 .../hc/client5/http/impl/auth/TestBasicScheme.java |  10 +-
 .../apache/hc/client5/http/utils/TestBase64.java   | 129 ++++++++++++++++
 pom.xml                                            |   6 -
 14 files changed, 407 insertions(+), 32 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/SHA256KeyHashingScheme.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/SHA256KeyHashingScheme.java
index 10a1c3f37..898b40569 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/SHA256KeyHashingScheme.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/memcached/SHA256KeyHashingScheme.java
@@ -29,7 +29,7 @@ package org.apache.hc.client5.http.impl.cache.memcached;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
-import org.apache.commons.codec.binary.Hex;
+import org.apache.hc.client5.http.utils.Hex;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
index a57605788..b07dc9ccf 100644
--- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/auth/BasicAuthTokenExtractor.java
@@ -29,9 +29,7 @@ package org.apache.hc.client5.testing.auth;
 
 import java.nio.charset.StandardCharsets;
 
-import org.apache.commons.codec.BinaryDecoder;
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.binary.Base64;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.ProtocolException;
@@ -49,9 +47,9 @@ public class BasicAuthTokenExtractor {
                 final String s = challengeResponse.substring(i + 1).trim();
                 try {
                     final byte[] credsRaw = s.getBytes(StandardCharsets.US_ASCII);
-                    final BinaryDecoder codec = new Base64();
+                    final Base64 codec = new Base64();
                     return new String(codec.decode(credsRaw), StandardCharsets.US_ASCII);
-                } catch (final DecoderException ex) {
+                } catch (final IllegalArgumentException ex) {
                     throw new ProtocolException("Malformed Basic credentials");
                 }
             }
diff --git a/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WindowsNegotiateScheme.java b/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WindowsNegotiateScheme.java
index 0daed7e7e..da1a629ff 100644
--- a/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WindowsNegotiateScheme.java
+++ b/httpclient5-win/src/main/java/org/apache/hc/client5/http/impl/win/WindowsNegotiateScheme.java
@@ -28,7 +28,7 @@ package org.apache.hc.client5.http.impl.win;
 
 import java.security.Principal;
 
-import org.apache.commons.codec.binary.Base64;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.RouteInfo;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthScheme;
diff --git a/httpclient5/pom.xml b/httpclient5/pom.xml
index 77695c9bd..9f234c88e 100644
--- a/httpclient5/pom.xml
+++ b/httpclient5/pom.xml
@@ -77,10 +77,6 @@
       <artifactId>log4j-core</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>commons-codec</groupId>
-      <artifactId>commons-codec</artifactId>
-    </dependency>
     <dependency>
       <groupId>org.brotli</groupId>
       <artifactId>dec</artifactId>
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
index 485605783..64407f08f 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java
@@ -36,7 +36,6 @@ import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
 import java.util.List;
 
-import org.apache.commons.codec.DecoderException;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.util.ByteArrayBuffer;
 
@@ -129,7 +128,7 @@ class HttpRFC7578Multipart extends AbstractMultipartFormat {
             return buffer.toByteArray();
         }
 
-        public byte[] decode(final byte[] bytes) throws DecoderException {
+        public byte[] decode(final byte[] bytes) {
             if (bytes == null) {
                 return null;
             }
@@ -138,7 +137,7 @@ class HttpRFC7578Multipart extends AbstractMultipartFormat {
                 final int b = bytes[i];
                 if (b == ESCAPE_CHAR) {
                     if (i >= bytes.length - 2) {
-                        throw new DecoderException("Invalid URL encoding: too short");
+                        throw new IllegalArgumentException("Invalid encoding: too short");
                     }
                     final int u = digit16(bytes[++i]);
                     final int l = digit16(bytes[++i]);
@@ -163,13 +162,13 @@ class HttpRFC7578Multipart extends AbstractMultipartFormat {
      *            The byte to be converted.
      * @return The numeric value represented by the character in radix 16.
      *
-     * @throws DecoderException
+     * @throws IllegalArgumentException
      *             Thrown when the byte is not valid per {@link Character#digit(char,int)}
      */
-    static int digit16(final byte b) throws DecoderException {
+    static int digit16(final byte b) {
         final int i = Character.digit((char) b, RADIX);
         if (i == -1) {
-            throw new DecoderException("Invalid URL encoding: not a valid digit (radix " + RADIX + "): " + b);
+            throw new IllegalArgumentException("Invalid encoding: not a valid digit (radix " + RADIX + "): " + b);
         }
         return i;
     }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
index 43cf32a61..87d11448c 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicScheme.java
@@ -39,7 +39,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
-import org.apache.commons.codec.binary.Base64;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.AuthScope;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
index 86e40a626..b8c6ed8ed 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/GGSSchemeBase.java
@@ -29,7 +29,7 @@ package org.apache.hc.client5.http.impl.auth;
 import java.net.UnknownHostException;
 import java.security.Principal;
 
-import org.apache.commons.codec.binary.Base64;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.DnsResolver;
 import org.apache.hc.client5.http.SystemDefaultDnsResolver;
 import org.apache.hc.client5.http.auth.AuthChallenge;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/NTLMEngineImpl.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/NTLMEngineImpl.java
index 3bf9f61ed..fa0548da0 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/NTLMEngineImpl.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/NTLMEngineImpl.java
@@ -40,7 +40,7 @@ import java.util.Random;
 import javax.crypto.Cipher;
 import javax.crypto.spec.SecretKeySpec;
 
-import org.apache.commons.codec.binary.Base64;
+import org.apache.hc.client5.http.utils.Base64;
 import org.apache.hc.client5.http.utils.ByteArrayBuilder;
 
 /**
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Base64.java b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Base64.java
new file mode 100644
index 000000000..20216a7ee
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Base64.java
@@ -0,0 +1,164 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.utils;
+
+
+import org.apache.hc.core5.annotation.Internal;
+
+import static java.util.Base64.getEncoder;
+import static java.util.Base64.getMimeDecoder;
+
+/**
+ * Provide implementations of the Base64 conversion methods from Commons Codec, delegating to the Java Base64
+ * implementation.
+ * <p>
+ * * <ul>Only the features currently used by HttpClient are implemented here rather than all the features of Commons Codec</ul>
+ * Notes:
+ * <p>
+ * <ul>Commons Codec accepts null inputs, so this is also accepted here. This is not done in the Java 8 implementation</ul>
+ * <ul>Decoding invalid input returns an empty value. The Java 8 implementation throws an exception, which is caught here</ul>
+ * <ul>Commons Codec decoders accept both standard and url-safe variants of input. As this is not a requirement for
+ * HttpClient, this is NOT implemented here.
+ * </ul>
+ * <p>
+ * This class is intended as in interim convenience. Any new code should use `java.util.Base64` directly.
+ */
+@Internal
+public class Base64 {
+    private static final Base64 CODEC = new Base64();
+    private static final byte[] EMPTY_BYTES = new byte[0];
+
+    /**
+     * Return an instance of the Base64 codec that use the regular Base64 alphabet
+     * (as opposed to the URL-safe alphabet). Note that unlike the Commons Codec version,
+     * thus class will NOT decode characters from URL-safe alphabet.
+     */
+    public Base64() {
+    }
+
+    /**
+     * Creates a Base64 codec used for decoding and encoding in URL-unsafe mode.
+     * <p>
+     * As HttpClient never uses a non-zero length, this feature is not implemented here.
+     */
+
+    public Base64(final int lineLength) {
+        if (lineLength != 0) {
+            throw new UnsupportedOperationException("Line breaks not supported");
+        }
+    }
+
+    /**
+     * Decodes Base64 data into octets.
+     * <p>
+     * <b>Note:</b> this method does NOT accept URL-safe encodings
+     */
+    public static byte[] decodeBase64(final byte[] base64) {
+        return CODEC.decode(base64);
+    }
+
+    /**
+     * Decodes a Base64 String into octets.
+     * <p>
+     * <b>Note:</b> this method does NOT accept URL-safe encodings
+     */
+
+    public static byte[] decodeBase64(final String base64) {
+        return CODEC.decode(base64);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm but does not chunk the output.
+     */
+
+    public static byte[] encodeBase64(final byte[] base64) {
+        return CODEC.encode(base64);
+    }
+
+    /**
+     * Encodes binary data using the base64 algorithm but does not chunk the output.
+     */
+
+    public static String encodeBase64String(final byte[] bytes) {
+        if (null == bytes) {
+            return null;
+        }
+
+        return getEncoder().encodeToString(bytes);
+    }
+
+    /**
+     * Decode Base64-encoded bytes to their original form, using specifications from this codec instance
+     */
+
+    public byte[] decode(final byte[] base64) {
+        if (null == base64) {
+            return null;
+        }
+
+        try {
+            return getMimeDecoder().decode(base64);
+        } catch (final IllegalArgumentException e) {
+            return EMPTY_BYTES;
+        }
+    }
+
+    /**
+     * Decode a Base64 String to its original form, using specifications from this codec instance
+     */
+
+    public byte[] decode(final String base64) {
+        if (null == base64) {
+            return null;
+        }
+
+        try {
+
+            // getMimeDecoder is used instead of getDecoder as it better matches the
+            // functionality of the default Commons Codec implementation (primarily more forgiving of strictly
+            // invalid inputs to decode)
+            // Code using java.util.Base64 directly should make a choice based on whether this forgiving nature is
+            // appropriate.
+
+            return getMimeDecoder().decode(base64);
+        } catch (final IllegalArgumentException e) {
+            return EMPTY_BYTES;
+        }
+    }
+
+    /**
+     * Encode bytes to their Base64 form, using specifications from this codec instance
+     */
+    public byte[] encode(final byte[] value) {
+        if (null == value) {
+            return null;
+        }
+        return getEncoder().encode(value);
+    }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Hex.java b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Hex.java
new file mode 100644
index 000000000..b3f6f6f4f
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/Hex.java
@@ -0,0 +1,94 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+
+package org.apache.hc.client5.http.utils;
+
+import org.apache.hc.core5.annotation.Internal;
+
+@Internal
+public class Hex {
+
+    private Hex() {
+    }
+
+    public static String encodeHexString(final byte[] bytes) {
+
+        final char[] out = new char[bytes.length * 2];
+
+        encodeHex(bytes, 0, bytes.length, DIGITS_LOWER, out, 0);
+        return new String(out);
+    }
+
+    //
+    // The following comes from commons-codec
+    // https://github.com/apache/commons-codec/blob/master/src/main/java/org/apache/commons/codec/binary/Hex.java
+
+    /**
+     * Used to build output as hex.
+     */
+
+    private static final char[] DIGITS_LOWER = {
+            '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    /**
+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+     *
+     * @param data       a byte[] to convert to hex characters
+     * @param dataOffset the position in {@code data} to start encoding from
+     * @param dataLen    the number of bytes from {@code dataOffset} to encode
+     * @param toDigits   the output alphabet (must contain at least 16 chars)
+     * @param out        a char[] which will hold the resultant appropriate characters from the alphabet.
+     * @param outOffset  the position within {@code out} at which to start writing the encoded characters.
+     */
+    private static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, final char[] toDigits,
+                                  final char[] out, final int outOffset) {
+        // two characters form the hex value.
+        for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) {
+            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
+            out[j++] = toDigits[0x0F & data[i]];
+        }
+    }
+
+    /*
+
+       // Can be replaced in Java 17 with the following:
+
+
+    private static final java.util.HexFormat HEX_FORMAT = HexFormat.of();
+
+    public static String encodeHex(byte[] bytes) {
+        return HEX_FORMAT.formatHex(bytes);
+    }
+
+
+     */
+
+
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java
index b8cb4e013..870482a1f 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java
@@ -29,7 +29,6 @@ package org.apache.hc.client5.http.entity.mime;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-import org.apache.commons.codec.DecoderException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -37,7 +36,7 @@ public class HttpRFC7578MultipartTest {
 
     @Test
     public void testPercentDecodingWithTooShortMessage() throws Exception {
-        Assertions.assertThrows(DecoderException.class, () ->
+        Assertions.assertThrows(java.lang.IllegalArgumentException.class, () ->
                 new HttpRFC7578Multipart.PercentCodec().decode("%".getBytes()));
     }
 
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java
index 5cd4b257b..252fa89af 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestBasicScheme.java
@@ -31,9 +31,9 @@ import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 import java.util.List;
 
-import org.apache.commons.codec.binary.Base64;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.auth.AuthScope;
@@ -55,6 +55,7 @@ import org.junit.jupiter.api.Test;
  * Basic authentication test cases.
  */
 public class TestBasicScheme {
+    private static final Base64.Encoder BASE64_ENC = Base64.getEncoder();
 
     private static AuthChallenge parse(final String s) throws ParseException {
         final CharArrayBuffer buffer = new CharArrayBuffer(s.length());
@@ -90,9 +91,10 @@ public class TestBasicScheme {
         Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null));
         final String authResponse = authscheme.generateAuthResponse(host, request, null);
 
-        final String expected = "Basic " + new String(
-                Base64.encodeBase64("testuser:testpass".getBytes(StandardCharsets.US_ASCII)),
-                StandardCharsets.US_ASCII);
+        final byte[] testCreds =  "testuser:testpass".getBytes(StandardCharsets.US_ASCII);
+
+        final String expected = "Basic " + BASE64_ENC.encodeToString(testCreds);
+
         Assertions.assertEquals(expected, authResponse);
         Assertions.assertEquals("test", authscheme.getRealm());
         Assertions.assertTrue(authscheme.isChallengeComplete());
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestBase64.java b/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestBase64.java
new file mode 100644
index 000000000..53307dbbf
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/utils/TestBase64.java
@@ -0,0 +1,129 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+
+package org.apache.hc.client5.http.utils;
+
+import org.junit.jupiter.api.Test;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class TestBase64 {
+
+    public static final char CHAR_ZERO = 0;
+    public static final String EMPTY_STR = "";
+    public static final byte[] EMPTY_BYTES = new byte[0];
+    public static final String NULL_STR = null;
+    public static final byte[] NULL_BYTE_ARRAY = null;
+    public static final String EMOJI = "\uD83D\uDE15";
+    public static final char SPACE = ' ';
+
+    private final Base64 target = new Base64();
+
+    @Test
+    void nullHandling() {
+        assertNull(target.decode(NULL_STR));
+        assertNull(target.decode(NULL_BYTE_ARRAY));
+        assertNull(Base64.decodeBase64(NULL_STR));
+        assertNull(Base64.decodeBase64(NULL_BYTE_ARRAY));
+
+        assertNull(target.encode(NULL_BYTE_ARRAY));
+        assertNull(Base64.encodeBase64(NULL_BYTE_ARRAY));
+        assertNull(Base64.encodeBase64String(NULL_BYTE_ARRAY));
+    }
+
+    @Test
+    void zeroLength() {
+        assertArrayEquals(EMPTY_BYTES, target.decode(EMPTY_STR));
+        assertArrayEquals(EMPTY_BYTES, target.decode(EMPTY_BYTES));
+        assertArrayEquals(EMPTY_BYTES, Base64.decodeBase64(EMPTY_STR));
+        assertArrayEquals(EMPTY_BYTES, Base64.decodeBase64(EMPTY_BYTES));
+
+        assertArrayEquals(EMPTY_BYTES, target.encode(EMPTY_BYTES));
+        assertArrayEquals(EMPTY_BYTES, Base64.encodeBase64(EMPTY_BYTES));
+        assertEquals(EMPTY_STR, Base64.encodeBase64String(EMPTY_BYTES));
+    }
+
+    @Test
+    void validValues() {
+        final byte[] unencodedBytes = "Hello World!".getBytes(US_ASCII);
+        checkDecode(unencodedBytes, "SGVsbG8gV29ybGQh");
+        checkEncode("SGVsbG8gV29ybGQh", unencodedBytes);
+    }
+
+    @Test
+    void decodeIgnoresEmbeddedInvalidChars() {
+        checkEquivalentDecode(fourOf("A"), " A A A A ");
+        checkEquivalentDecode(fourOf("A"), "AA" + EMOJI + "AA");
+    }
+
+    @Test
+    void decodeInvalid() {
+        checkDecode(EMPTY_BYTES, fourOf(EMOJI));
+        checkDecode(EMPTY_BYTES, "A");
+        checkDecode(EMPTY_BYTES, "A===");
+        checkDecode(EMPTY_BYTES, fourOf(SPACE));
+        checkDecode(EMPTY_BYTES, fourOf('='));
+        checkDecode(EMPTY_BYTES, fourOf('@'));
+        checkDecode(EMPTY_BYTES, fourOf(CHAR_ZERO));
+    }
+
+    @Test
+    void decodeUnpadded() {
+        checkEquivalentDecode("AA==", "AA");
+    }
+
+    private void checkDecode(final byte[] expectedDecoded, final String testInput) {
+        final byte[] decoded = target.decode(testInput);
+        assertArrayEquals(expectedDecoded, decoded);
+    }
+
+    private void checkEncode(final String expectedEncoded, final byte[] testInput) {
+        final byte[] encoded = target.encode(testInput);
+        assertEquals(expectedEncoded, new String(encoded, US_ASCII));
+    }
+
+    private void checkEquivalentDecode(final String expectedEquivalentTo, final String testInput) {
+        final byte[] decoded = target.decode(testInput);
+
+        final byte[] expectedDecoded = java.util.Base64.getDecoder().decode(expectedEquivalentTo);
+        assertArrayEquals(expectedDecoded, decoded);
+    }
+
+    private static String fourOf(final char c) {
+        final String charStr = String.valueOf(c);
+        return fourOf(charStr);
+    }
+
+    private static String fourOf(final String str) {
+        return str + str + str + str;
+    }
+
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3d12b1b94..6224f7e69 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,7 +64,6 @@
     <maven.compiler.target>1.8</maven.compiler.target>
     <httpcore.version>5.2-beta1</httpcore.version>
     <log4j.version>2.17.0</log4j.version>
-    <commons-codec.version>1.15</commons-codec.version>
     <brotli.version>0.1.2</brotli.version>
     <conscrypt.version>2.5.2</conscrypt.version>
     <ehcache.version>3.9.6</ehcache.version>
@@ -144,11 +143,6 @@
         <artifactId>log4j-core</artifactId>
         <version>${log4j.version}</version>
       </dependency>
-      <dependency>
-        <groupId>commons-codec</groupId>
-        <artifactId>commons-codec</artifactId>
-        <version>${commons-codec.version}</version>
-      </dependency>
       <dependency>
         <groupId>org.brotli</groupId>
         <artifactId>dec</artifactId>