You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kafka.apache.org by rs...@apache.org on 2018/01/30 11:49:56 UTC
[kafka] branch 1.0 updated: KAFKA-6464: Fix Base64URL encode
padding issue under JRE 1.7 (#4455)
This is an automated email from the ASF dual-hosted git repository.
rsivaram pushed a commit to branch 1.0
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/1.0 by this push:
new 3521b37 KAFKA-6464: Fix Base64URL encode padding issue under JRE 1.7 (#4455)
3521b37 is described below
commit 3521b3725a75508e10d14608350af5516eea5acd
Author: Ron Dagostino <rn...@gmail.com>
AuthorDate: Tue Jan 30 06:09:06 2018 -0500
KAFKA-6464: Fix Base64URL encode padding issue under JRE 1.7 (#4455)
The org.apache.kafka.common.utils.Base64 class defers Base64 encoding/decoding to the java.util.Base64 class beginning with JRE 1.8 but leverages javax.xml.bind.DatatypeConverter under JRE 1.7. The implementation of the encodeToString(bytes[]) method returned under JRE 1.7 by Base64.urlEncoderNoPadding() blindly removed the last two trailing characters of the Base64 encoding under the assumption that they would always be the string "==" but that is incorrect; padding can be "=", "==" [...]
The commit also adds a Base64.urlDecoder() method that defers to java.util.Base64 under JRE 1.8+ but leverages javax.xml.bind.DatatypeConverter under JRE 1.7.
Finally, there is a unit test to confirm that encode/decode are inverses in both the Base64 and Base64URL cases.
---
.../java/org/apache/kafka/common/utils/Base64.java | 69 ++++++++++++++++++++--
.../org/apache/kafka/common/utils/Base64Test.java | 45 ++++++++++++++
2 files changed, 109 insertions(+), 5 deletions(-)
diff --git a/clients/src/main/java/org/apache/kafka/common/utils/Base64.java b/clients/src/main/java/org/apache/kafka/common/utils/Base64.java
index e06e1ee..3ab4900 100644
--- a/clients/src/main/java/org/apache/kafka/common/utils/Base64.java
+++ b/clients/src/main/java/org/apache/kafka/common/utils/Base64.java
@@ -50,6 +50,10 @@ public final class Base64 {
return FACTORY.decoder();
}
+ public static Decoder urlDecoder() {
+ return FACTORY.urlDecoder();
+ }
+
/* Contains a subset of methods from java.util.Base64.Encoder (introduced in Java 8) */
public interface Encoder {
String encodeToString(byte[] bytes);
@@ -63,6 +67,7 @@ public final class Base64 {
private interface Factory {
Encoder urlEncoderNoPadding();
Encoder encoder();
+ Decoder urlDecoder();
Decoder decoder();
}
@@ -71,10 +76,12 @@ public final class Base64 {
// Static final MethodHandles are optimised better by HotSpot
private static final MethodHandle URL_ENCODE_NO_PADDING;
private static final MethodHandle ENCODE;
+ private static final MethodHandle URL_DECODE;
private static final MethodHandle DECODE;
private static final Encoder URL_ENCODER_NO_PADDING;
private static final Encoder ENCODER;
+ private static final Decoder URL_DECODER;
private static final Decoder DECODER;
static {
@@ -123,6 +130,17 @@ public final class Base64 {
throw (RuntimeException) throwable;
}
+ MethodHandle getUrlDecoder = lookup.findStatic(base64Class, "getUrlDecoder",
+ MethodType.methodType(juDecoderClass));
+ MethodHandle urlDecode = lookup.findVirtual(juDecoderClass, "decode",
+ MethodType.methodType(byte[].class, String.class));
+ try {
+ URL_DECODE = urlDecode.bindTo(getUrlDecoder.invoke());
+ } catch (Throwable throwable) {
+ // Invoked method doesn't throw checked exceptions, so safe to cast
+ throw (RuntimeException) throwable;
+ }
+
URL_ENCODER_NO_PADDING = new Encoder() {
@Override
public String encodeToString(byte[] bytes) {
@@ -147,6 +165,18 @@ public final class Base64 {
}
};
+ URL_DECODER = new Decoder() {
+ @Override
+ public byte[] decode(String string) {
+ try {
+ return (byte[]) URL_DECODE.invokeExact(string);
+ } catch (Throwable throwable) {
+ // Invoked method doesn't throw checked exceptions, so safe to cast
+ throw (RuntimeException) throwable;
+ }
+ }
+ };
+
DECODER = new Decoder() {
@Override
public byte[] decode(String string) {
@@ -179,6 +209,11 @@ public final class Base64 {
public Decoder decoder() {
return DECODER;
}
+
+ @Override
+ public Decoder urlDecoder() {
+ return URL_DECODER;
+ }
}
private static class Java7Factory implements Factory {
@@ -205,12 +240,15 @@ public final class Base64 {
@Override
public String encodeToString(byte[] bytes) {
+ if (bytes.length == 0)
+ return "";
String base64EncodedUUID = Java7Factory.encodeToString(bytes);
- //Convert to URL safe variant by replacing + and / with - and _ respectively.
- String urlSafeBase64EncodedUUID = base64EncodedUUID.replace("+", "-")
- .replace("/", "_");
- // Remove the "==" padding at the end.
- return urlSafeBase64EncodedUUID.substring(0, urlSafeBase64EncodedUUID.length() - 2);
+ // Convert to URL safe variant by replacing + and / with - and _ respectively.
+ String urlSafeBase64EncodedUUID = base64EncodedUUID.replace("+", "-").replace("/", "_");
+ // Remove any "=" or "==" padding at the end.
+ // Note that length will be at least 4 here.
+ int index = urlSafeBase64EncodedUUID.indexOf('=', urlSafeBase64EncodedUUID.length() - 2);
+ return index > 0 ? urlSafeBase64EncodedUUID.substring(0, index) : urlSafeBase64EncodedUUID;
}
};
@@ -234,6 +272,22 @@ public final class Base64 {
}
};
+ public static final Decoder URL_DECODER = new Decoder() {
+ @Override
+ public byte[] decode(String string) {
+ try {
+ // Convert from URL safe variant by replacing - and _ with + and / respectively,
+ // and append "=" or "==" padding; then decode.
+ String unpadded = string.replace("-", "+").replace("_", "/");
+ int padLength = 4 - (unpadded.length() & 3);
+ return (byte[]) PARSE.invokeExact(padLength > 2 ? unpadded : unpadded + "==".substring(0, padLength));
+ } catch (Throwable throwable) {
+ // Invoked method doesn't throw checked exceptions, so safe to cast
+ throw (RuntimeException) throwable;
+ }
+ }
+ };
+
private static String encodeToString(byte[] bytes) {
try {
return (String) PRINT.invokeExact(bytes);
@@ -254,6 +308,11 @@ public final class Base64 {
}
@Override
+ public Decoder urlDecoder() {
+ return URL_DECODER;
+ }
+
+ @Override
public Decoder decoder() {
return DECODER;
}
diff --git a/clients/src/test/java/org/apache/kafka/common/utils/Base64Test.java b/clients/src/test/java/org/apache/kafka/common/utils/Base64Test.java
new file mode 100644
index 0000000..9dcd15a
--- /dev/null
+++ b/clients/src/test/java/org/apache/kafka/common/utils/Base64Test.java
@@ -0,0 +1,45 @@
+/*
+ * 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.kafka.common.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.kafka.common.utils.Base64.Decoder;
+import org.apache.kafka.common.utils.Base64.Encoder;
+import org.junit.Test;
+
+public class Base64Test {
+
+ @Test
+ public void testBase64UrlEncodeDecode() {
+ confirmInversesForAllThreePaddingCases(Base64.urlEncoderNoPadding(), Base64.urlDecoder());
+ }
+
+ @Test
+ public void testBase64EncodeDecode() {
+ confirmInversesForAllThreePaddingCases(Base64.encoder(), Base64.decoder());
+ }
+
+ private static void confirmInversesForAllThreePaddingCases(Encoder encoder, Decoder decoder) {
+ for (String text : new String[] {"", "a", "ab", "abc"}) {
+ assertEquals(text, new String(decoder.decode(encoder.encodeToString(text.getBytes(StandardCharsets.UTF_8))),
+ StandardCharsets.UTF_8));
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
rsivaram@apache.org.