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

vavrtom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git


The following commit(s) were added to refs/heads/main by this push:
     new 6390ef17a1 QPID-8583: [Broker-J] Privacy Violation: Heap Inspection 
(#124)
6390ef17a1 is described below

commit 6390ef17a138abe4c6ec4a742472640d0d7b02bc
Author: Daniil Kirilyuk <[email protected]>
AuthorDate: Wed Apr 20 09:37:05 2022 +0200

    QPID-8583: [Broker-J] Privacy Violation: Heap Inspection (#124)
    
    * QPID-8583: [Broker-J] Privacy Violation: Heap Inspection
    
    * QPID-8583: [Broker-J] Added new line to the end of ClearableCharSequence
    
    Co-authored-by: vavrtom <[email protected]>
---
 .../model/ConfiguredDerivedInjectedAttribute.java  |  10 +-
 .../model/ConfiguredObjectMethodAttribute.java     |  19 +-
 .../model/ConfiguredSettableInjectedAttribute.java |  11 +-
 .../AbstractScramAuthenticationManager.java        |   4 +
 .../crammd5/CramMd5Base64HashedNegotiator.java     |  15 +-
 .../sasl/crammd5/CramMd5Base64HexNegotiator.java   |  17 +-
 .../qpid/server/util/ClearableCharSequence.java    |  68 +++
 .../java/org/apache/qpid/server/util/Strings.java  | 522 ++++++++++++---------
 .../org/apache/qpid/server/util/StringsTest.java   |  82 ++++
 .../auth/BasicAuthPreemptiveAuthenticator.java     |  43 +-
 10 files changed, 536 insertions(+), 255 deletions(-)

diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
index ff88d7ea38..3334ffab02 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
@@ -28,7 +28,9 @@ import java.util.regex.Pattern;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.qpid.server.util.ClearableCharSequence;
 import org.apache.qpid.server.util.ServerScopedRuntimeException;
+import org.apache.qpid.server.util.Strings;
 
 public class ConfiguredDerivedInjectedAttribute<C extends ConfiguredObject, T>
         extends ConfiguredObjectInjectedAttributeOrStatistic<C, T> implements 
ConfiguredObjectInjectedAttribute<C, T>
@@ -157,9 +159,11 @@ public class ConfiguredDerivedInjectedAttribute<C extends 
ConfiguredObject, T>
     @Override
     public boolean isSecureValue(final Object value)
     {
-        Pattern filter;
-        return isSecure() &&
-               ((filter = getSecureValueFilter()) == null || 
filter.matcher(String.valueOf(value)).matches());
+        try (final ClearableCharSequence charSequence = 
Strings.toCharSequence(value))
+        {
+            final Pattern filter = getSecureValueFilter();
+            return isSecure() && (filter == null || 
filter.matcher(charSequence).matches());
+        }
     }
 
 
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
index dec0c526d2..b137f08359 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
@@ -23,6 +23,9 @@ package org.apache.qpid.server.model;
 import java.lang.reflect.Method;
 import java.util.regex.Pattern;
 
+import org.apache.qpid.server.util.ClearableCharSequence;
+import org.apache.qpid.server.util.Strings;
+
 public abstract class ConfiguredObjectMethodAttribute<C extends 
ConfiguredObject, T>
         extends  ConfiguredObjectMethodAttributeOrStatistic<C,T>
         implements ConfiguredObjectAttribute<C,T>
@@ -38,21 +41,13 @@ public abstract class ConfiguredObjectMethodAttribute<C 
extends ConfiguredObject
     }
 
     @Override
-    public boolean isSecureValue(Object value)
+    public boolean isSecureValue(final Object value)
     {
-        if (isSecure())
+        try (final ClearableCharSequence charSequence = 
Strings.toCharSequence(value))
         {
-            Pattern filter = getSecureValueFilter();
-            if (filter == null)
-            {
-                return  true;
-            }
-            else
-            {
-                return filter.matcher(String.valueOf(value)).matches();
-            }
+            final Pattern filter = getSecureValueFilter();
+            return isSecure() && (filter == null || 
filter.matcher(charSequence).matches());
         }
-        return false;
     }
 
 }
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
index 0361b023fc..d09ed2ecd0 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
@@ -35,6 +35,9 @@ import java.util.regex.Pattern;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.qpid.server.util.ClearableCharSequence;
+import org.apache.qpid.server.util.Strings;
+
 public class ConfiguredSettableInjectedAttribute<C extends ConfiguredObject, T>
         extends ConfiguredObjectInjectedAttributeOrStatistic<C,T> implements 
ConfiguredSettableAttribute<C,T>, ConfiguredObjectInjectedAttribute<C,T>
 {
@@ -229,9 +232,11 @@ public class ConfiguredSettableInjectedAttribute<C extends 
ConfiguredObject, T>
     @Override
     public boolean isSecureValue(final Object value)
     {
-        Pattern filter;
-        return isSecure() &&
-               ((filter = getSecureValueFilter()) == null || 
filter.matcher(String.valueOf(value)).matches());
+        try (final ClearableCharSequence charSequence = 
Strings.toCharSequence(value))
+        {
+            final Pattern filter = getSecureValueFilter();
+            return isSecure() && (filter == null || 
filter.matcher(charSequence).matches());
+        }
     }
 
     @Override
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
index abc6cbf7b9..ce433073f1 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
@@ -181,6 +181,10 @@ public abstract class AbstractScramAuthenticationManager<X 
extends AbstractScram
             {
                 throw new IllegalArgumentException(e);
             }
+            finally
+            {
+                Strings.clearByteArray(saltedPassword);
+            }
         }
         else if (passwordFields.length == 4)
         {
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
index 91c9daa1e6..67e450daec 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
@@ -29,13 +29,20 @@ public class CramMd5Base64HashedNegotiator extends 
AbstractCramMd5Negotiator
     public static final String MECHANISM = "CRAM-MD5-HASHED";
     private static final PasswordTransformer BASE64_PASSWORD_TRANSFORMER = 
passwordData ->
     {
-        final byte[] passwordBytes = Strings.decodePrivateBase64(new 
String(passwordData), "CRAM MD5 hashed password");
+        final byte[] passwordBytes = Strings.decodeCharArray(passwordData, 
"CRAM MD5 hashed password");
         final char[] password = new char[passwordBytes.length];
-        for (int i = 0; i < passwordBytes.length; i++)
+        try
         {
-            password[i] = (char) passwordBytes[i];
+            for (int i = 0; i < passwordBytes.length; i++)
+            {
+                password[i] = (char) passwordBytes[i];
+            }
+            return password;
+        }
+        finally
+        {
+            Strings.clearByteArray(passwordBytes);
         }
-        return password;
     };
 
     public CramMd5Base64HashedNegotiator(final 
PasswordCredentialManagingAuthenticationProvider<?> authenticationProvider,
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
index edcbff548f..39aa6254c6 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
@@ -31,14 +31,21 @@ public class CramMd5Base64HexNegotiator extends 
AbstractCramMd5Negotiator
             {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 
'd', 'e', 'f'};
     private static final PasswordTransformer BASE64_HEX_PASSWORD_TRANSFORMER = 
passwordData ->
     {
-        final byte[] passwordBytes = Strings.decodePrivateBase64(new 
String(passwordData), "CRAM MD5 hex password");
+        final byte[] passwordBytes = Strings.decodeCharArray(passwordData, 
"CRAM MD5 hex password");
         final char[] password = new char[passwordBytes.length * 2];
-        for (int i = 0; i < passwordBytes.length; i++)
+        try
         {
-            password[2 * i] = HEX_CHARACTERS[(((int) passwordBytes[i]) & 0xf0) 
>> 4];
-            password[(2 * i) + 1] = HEX_CHARACTERS[(((int) passwordBytes[i]) & 
0x0f)];
+            for (int i = 0; i < passwordBytes.length; i++)
+            {
+                password[2 * i] = HEX_CHARACTERS[(((int) passwordBytes[i]) & 
0xf0) >> 4];
+                password[(2 * i) + 1] = HEX_CHARACTERS[(((int) 
passwordBytes[i]) & 0x0f)];
+            }
+            return password;
+        }
+        finally
+        {
+            Strings.clearByteArray(passwordBytes);
         }
-        return password;
     };
 
     public CramMd5Base64HexNegotiator(final 
PasswordCredentialManagingAuthenticationProvider<?> authenticationProvider,
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
 
b/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
new file mode 100644
index 0000000000..c8365e2009
--- /dev/null
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * 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.qpid.server.util;
+
+/**
+ * CharSequence backed up by a StringBuilder and implementing AutoCloseable.
+ * On close() StringBuilder is cleared.
+ */
+public class ClearableCharSequence implements CharSequence, AutoCloseable
+{
+    private StringBuilder _stringBuilder = new StringBuilder();
+
+    public ClearableCharSequence(final Object object)
+    {
+        _stringBuilder.append(object == null ? "null".toCharArray() : 
object.toString().toCharArray());
+    }
+
+    @Override
+    public void close()
+    {
+        final int length = _stringBuilder.length();
+        _stringBuilder.setLength(0);
+        _stringBuilder.setLength(length);
+        _stringBuilder = null;
+    }
+
+    @Override
+    public int length()
+    {
+        return _stringBuilder.length();
+    }
+
+    @Override
+    public char charAt(final int index)
+    {
+        return _stringBuilder.charAt(index);
+    }
+
+    @Override
+    public CharSequence subSequence(final int start, final int end)
+    {
+        return _stringBuilder.subSequence(start, end);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _stringBuilder.toString();
+    }
+}
diff --git a/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java 
b/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
index b62fe7f4bc..1d48b7fcdf 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
@@ -20,8 +20,9 @@
  */
 package org.apache.qpid.server.util;
 
-import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collections;
@@ -29,36 +30,93 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
+import java.util.Objects;
 import java.util.Set;
 import java.util.Stack;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-
 /**
- * Strings
- *
+ * String utilities
  */
-
 public final class Strings
 {
+    /**
+     * Utility class shouldn't be instantiated directly
+     */
     private Strings()
     {
+
     }
 
+    /**
+     * Empty byte array
+     */
     private static final byte[] EMPTY = new byte[0];
 
-    private static final ThreadLocal<char[]> charbuf = new 
ThreadLocal<char[]>()
+    /**
+     * Thread bound character buffer
+     */
+    private static final ThreadLocal<char[]> CHAR_BUFFER = 
ThreadLocal.withInitial(() -> new char[4096]);
+
+    /**
+     * Variable regexp pattern
+     */
+    private static final Pattern VAR = 
Pattern.compile("\\$\\{([^}]*)}|\\$(\\$)");
+
+    /**
+     * Null resolver, always returning null
+     */
+    private static final Resolver NULL_RESOLVER = (variable, resolver) -> null;
+
+    /**
+     * Environment variable resolver
+     */
+    public static final Resolver ENV_VARS_RESOLVER = (variable, resolver) -> 
System.getenv(variable);
+
+    /**
+     * System property resolver
+     */
+    public static final Resolver JAVA_SYS_PROPS_RESOLVER = (variable, 
resolver) -> System.getProperty(variable);
+
+    /**
+     * System resolver chaining environment variable and system property 
resolvers
+     */
+    public static final Resolver SYSTEM_RESOLVER = 
chain(JAVA_SYS_PROPS_RESOLVER, ENV_VARS_RESOLVER);
+
+    /**
+     * Chains several resolvers into a ChainedResolver instance
+     *
+     * @param resolvers Resolvers to be chained
+     *
+     * @return Resulting ChainedResolver
+     */
+    public static Resolver chain(final Resolver... resolvers)
     {
-        @Override
-        public char[] initialValue()
+        Resolver resolver;
+        if(resolvers.length == 0)
         {
-            return new char[4096];
+            resolver = NULL_RESOLVER;
         }
-    };
+        else
+        {
+            resolver = resolvers[resolvers.length - 1];
+            for (int i = resolvers.length - 2; i >= 0; i--)
+            {
+                resolver = new ChainedResolver(resolvers[i], resolver);
+            }
+        }
+        return resolver;
+    }
 
-    public static final byte[] toUTF8(String str)
+    /**
+     * Converts string to the UTF8 encoded byte array
+     *
+     * @param str Source string
+     *
+     * @return Byte array
+     */
+    public static byte[] toUTF8(final String str)
     {
         if (str == null)
         {
@@ -67,11 +125,11 @@ public final class Strings
         else
         {
             final int size = str.length();
-            char[] chars = charbuf.get();
+            char[] chars = CHAR_BUFFER.get();
             if (size > chars.length)
             {
                 chars = new char[Math.max(size, 2*chars.length)];
-                charbuf.set(chars);
+                CHAR_BUFFER.set(chars);
             }
 
             str.getChars(0, size, chars, 0);
@@ -80,55 +138,23 @@ public final class Strings
             {
                 if (chars[i] > 127)
                 {
-                    try
-                    {
-                        return str.getBytes("UTF-8");
-                    }
-                    catch (UnsupportedEncodingException e)
-                    {
-                        throw new RuntimeException(e);
-                    }
+                    return str.getBytes(StandardCharsets.UTF_8);
                 }
-
                 bytes[i] = (byte) chars[i];
             }
             return bytes;
         }
     }
 
-    public static final String fromUTF8(byte[] bytes)
-    {
-        try
-        {
-            return new String(bytes, "UTF-8");
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static final Pattern VAR = 
Pattern.compile("(?:\\$\\{([^\\}]*)\\})|(?:\\$(\\$))");
-
-    public static Resolver chain(Resolver... resolvers)
-    {
-        Resolver resolver;
-        if(resolvers.length == 0)
-        {
-            resolver =  NULL_RESOLVER;
-        }
-        else
-        {
-            resolver = resolvers[resolvers.length - 1];
-            for (int i = resolvers.length - 2; i >= 0; i--)
-            {
-                resolver = new ChainedResolver(resolvers[i], resolver);
-            }
-        }
-        return resolver;
-    }
-
-    public static byte[] decodePrivateBase64(String base64String, String 
description)
+    /**
+     * Decodes base64 encoded string into a byte array
+     *
+     * @param base64String Base64 encoded string
+     * @param description String description provided for logging purposes
+     *
+     * @return Resulting byte array
+     */
+    public static byte[] decodePrivateBase64(final String base64String, final 
String description)
     {
         if (isInvalidBase64String(base64String))
         {
@@ -136,148 +162,151 @@ public final class Strings
             throw new IllegalArgumentException("Cannot convert " + description 
+
                     " string to a byte[] - it does not appear to be base64 
data");
         }
-
-        return Base64.getDecoder().decode(base64String);
-    }
-
-    public static byte[] decodeBase64(String base64String)
-    {
-        if (isInvalidBase64String(base64String))
-        {
-            throw new IllegalArgumentException("Cannot convert string '" + 
base64String +
-                    "' to a byte[] - it does not appear to be base64 data");
-        }
-
         return Base64.getDecoder().decode(base64String);
     }
 
-    private static boolean isInvalidBase64String(String base64String)
-    {
-        return !base64String.replaceAll("\\s", 
"").matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
-    }
-
-    public static interface Resolver
-    {
-        String resolve(String variable, final Resolver resolver);
-    }
-
-    private static final Resolver NULL_RESOLVER =
-            new Resolver()
-            {
-                @Override
-                public String resolve(final String variable, final Resolver 
resolver)
-                {
-                    return null;
-                }
-            };
-
-    public static class MapResolver implements Resolver
+    /**
+     * Decodes base64 encoded char array into a byte array
+     *
+     * @param base64Chars Base64 encoded char array
+     * @param description String description provided for logging purposes
+     *
+     * @return Resulting byte array
+     */
+    public static byte[] decodeCharArray(final char[] base64Chars, final 
String description)
     {
-
-        private final Map<String,String> map;
-
-        public MapResolver(Map<String,String> map)
-        {
-            this.map = map;
-        }
-
-        @Override
-        public String resolve(String variable, final Resolver resolver)
+        if (base64Chars == null)
         {
-            return map.get(variable);
+            return null;
         }
-    }
-
-    public static class PropertiesResolver implements Resolver
-    {
-
-        private final Properties properties;
-
-        public PropertiesResolver(Properties properties)
+        try
         {
-            this.properties = properties;
+            final CharBuffer charBuffer = CharBuffer.wrap(base64Chars);
+            final ByteBuffer byteBuffer = 
StandardCharsets.UTF_8.encode(charBuffer);
+            return Base64.getDecoder().decode(byteBuffer).array();
         }
-
-        @Override
-        public String resolve(String variable, final Resolver resolver)
+        catch (IllegalArgumentException e)
         {
-            return properties.getProperty(variable);
+            // do not add base64String to exception message as it can contain 
private data
+            throw new IllegalArgumentException("Cannot convert "
+                                               + description
+                                               + " string to a byte[] - it 
does not appear to be base64 data");
         }
     }
 
-    public static class ChainedResolver implements Resolver
+    /**
+     * Fills byte arrays with blank characters
+     *
+     * @param bytes Byte arrays to be cleared
+     */
+    public static void clearByteArray(byte[]... bytes)
     {
-        private final Resolver primary;
-        private final Resolver secondary;
-
-        public ChainedResolver(Resolver primary, Resolver secondary)
+        for (final byte[] array : bytes)
         {
-            this.primary = primary;
-            this.secondary = secondary;
-        }
-
-        @Override
-        public String resolve(String variable, final Resolver resolver)
-        {
-            String result = primary.resolve(variable, resolver);
-            if (result == null)
+            if (array != null)
             {
-                result = secondary.resolve(variable, resolver);
+                Arrays.fill(array, (byte) 0);
             }
-            return result;
         }
     }
 
-    public static final Resolver ENV_VARS_RESOLVER = new Resolver()
-        {
-            @Override
-            public String resolve(final String variable, final Resolver 
resolver)
-            {
-                return System.getenv(variable);
-            }
-        };
-
+    /**
+     * Converts an object to the ClearableCharSequence
+     *
+     * @param object Object to convert
+     *
+     * @return ClearableCharSequence instance
+     */
+    public static ClearableCharSequence toCharSequence(final Object object)
+    {
+        return new ClearableCharSequence(object);
+    }
 
-    public static final Resolver JAVA_SYS_PROPS_RESOLVER = new Resolver()
+    /**
+     * Decodes base64 encoded string into a byte array
+     *
+     * @param base64String Base64 encoded string
+     *
+     * @return Resulting byte array
+     */
+    public static byte[] decodeBase64(final String base64String)
     {
-        @Override
-        public String resolve(final String variable, final Resolver resolver)
+        if (isInvalidBase64String(base64String))
         {
-            return System.getProperty(variable);
+            throw new IllegalArgumentException("Cannot convert string '" + 
base64String +
+                    "' to a byte[] - it does not appear to be base64 data");
         }
-    };
-
-
-    public static final Resolver SYSTEM_RESOLVER = 
chain(JAVA_SYS_PROPS_RESOLVER, ENV_VARS_RESOLVER);
+        return Base64.getDecoder().decode(base64String);
+    }
 
-    public static final String expand(String input)
+    /**
+     * Checks if string is valid base64 encoded string or not
+     *
+     * @param base64String Base64 encoded string
+     *
+     * @return True when parameter passed is valid base64 encoded string, 
false otherwise
+     */
+    private static boolean isInvalidBase64String(final String base64String)
     {
-        return expand(input, SYSTEM_RESOLVER);
+        return !base64String.replaceAll("\\s", 
"").matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
     }
 
-    public static final String expand(String input, Resolver resolver)
+    /**
+     * Expands string replacing variables it contains with variable values
+     *
+     * @param input Source string
+     * @param resolver Resolver to use
+     *
+     * @return Expanded string
+     */
+    public static String expand(final String input, final Resolver resolver)
     {
-        return expand(input, resolver, new Stack<String>(),true);
+        return expand(input, resolver, new Stack<>(),true);
     }
-    public static final String expand(String input, boolean failOnUnresolved, 
Resolver... resolvers)
+
+    /**
+     * Expands string replacing variables it contains with variable values
+     *
+     * @param input Source string
+     * @param failOnUnresolved Boolean flag defining if an exception should be 
thrown in case of failed resolution
+     * @param resolvers Resolvers to use
+     *
+     * @return Expanded string
+     */
+    public static String expand(final String input, final boolean 
failOnUnresolved, final Resolver... resolvers)
     {
-        return expand(input, chain(resolvers), new Stack<String>(), 
failOnUnresolved);
+        return expand(input, chain(resolvers), new Stack<>(), 
failOnUnresolved);
     }
 
-    private static final String expand(String input, Resolver resolver, 
Stack<String> stack, boolean failOnUnresolved)
+    /**
+     * Expands string replacing variables it contains with variable values
+     *
+     * @param input Source string
+     * @param resolver Resolver
+     * @param stack Stack containing variable chain
+     * @param failOnUnresolved Boolean flag defining if an exception should be 
thrown in case of failed resolution
+     *
+     * @return Expanded string
+     */
+    private static String expand(
+        final String input,
+        final Resolver resolver,
+        final Stack<String> stack,
+        final boolean failOnUnresolved
+    )
     {
         if (input == null)
         {
             return null;
         }
-        Matcher m = VAR.matcher(input);
-        StringBuffer result = new StringBuffer();
+        final Matcher m = VAR.matcher(input);
+        final StringBuffer result = new StringBuffer();
         while (m.find())
         {
-            String var = m.group(1);
+            final String var = m.group(1);
             if (var == null)
             {
-                String esc = m.group(2);
+                final String esc = m.group(2);
                 if ("$".equals(esc))
                 {
                     m.appendReplacement(result, Matcher.quoteReplacement("$"));
@@ -296,10 +325,22 @@ public final class Strings
         return result.toString();
     }
 
-    private static final String resolve(String var,
-                                        Resolver resolver,
-                                        Stack<String> stack,
-                                        final boolean failOnUnresolved)
+    /**
+     * Resolves variable
+     *
+     * @param var Variable name
+     * @param resolver Resolver
+     * @param stack Stack containing variable chain
+     * @param failOnUnresolved Boolean flag defining if an exception should be 
thrown in case of failed resolution
+     *
+     * @return Resolved variable value
+     */
+    private static String resolve(
+        final String var,
+        final Resolver resolver,
+        final Stack<String> stack,
+        final boolean failOnUnresolved
+    )
     {
         if (stack.contains(var))
         {
@@ -308,10 +349,10 @@ public final class Strings
                                stack));
         }
 
-        String result = resolver.resolve(var, resolver);
+        final String result = resolver.resolve(var, resolver);
         if (result == null)
         {
-            if(failOnUnresolved)
+            if (failOnUnresolved)
             {
                 throw new IllegalArgumentException("no such variable: " + var);
             }
@@ -332,57 +373,60 @@ public final class Strings
         }
     }
 
-    public static final String join(String sep, Iterable items)
+    /**
+     * Joins string representation of an object iterable
+     *
+     * @param sep Separator
+     * @param items Object iterable
+     *
+     * @return Resulting string
+     */
+    public static String join(final String sep, final Iterable<?> items)
     {
-        StringBuilder result = new StringBuilder();
-
-        for (Object o : items)
+        Objects.requireNonNull(sep, "Separator must be not null");
+        Objects.requireNonNull(items, "Items must be not null");
+        final StringBuilder result = new StringBuilder();
+        for (final Object object : items)
         {
             if (result.length() > 0)
             {
                 result.append(sep);
             }
-            result.append(o.toString());
+            result.append(object == null ? "null" : object.toString());
         }
-
         return result.toString();
     }
 
-    public static final String join(String sep, Object[] items)
+    /**
+     * Joins string representation of an object array
+     *
+     * @param sep Separator
+     * @param items Object array
+     *
+     * @return Resulting string
+     */
+    public static String join(final String sep, final Object[] items)
     {
+        Objects.requireNonNull(items, "Items must be not null");
         return join(sep, Arrays.asList(items));
     }
 
-    public static final List<String> split(String listAsString)
+    /**
+     * Splits source string into a liast of tokens separated by comma
+     *
+     * @param listAsString Source string
+     *
+     * @return List of tokens
+     */
+    public static List<String> split(final String listAsString)
     {
-        if(listAsString != null && !"".equals(listAsString))
+        if (listAsString != null && !"".equals(listAsString))
         {
             return Arrays.asList(listAsString.split("\\s*,\\s*"));
         }
         return Collections.emptyList();
     }
 
-    public static String printMap(Map<String,Object> map)
-    {
-        StringBuilder sb = new StringBuilder();
-        sb.append("<");
-        if (map != null)
-        {
-            for(Map.Entry<String,Object> entry : map.entrySet())
-            {
-                sb.append(entry.getKey()).append(" = 
").append(entry.getValue()).append(" ");
-            }
-        }
-        sb.append(">");
-        return sb.toString();
-    }
-
-
-    public static Resolver createSubstitutionResolver(String prefix, 
LinkedHashMap<String,String> substitutions)
-    {
-        return new StringSubstitutionResolver(prefix, substitutions);
-    }
-
     /**
      * Dumps bytes in the textual format used by UNIX od(1) in hex (x4) mode 
i.e. {@code od -Ax -tx1 -v}.
      *
@@ -391,11 +435,11 @@ public final class Strings
      *
      * @param buf - buffer to be dumped.  Buffer will be unchanged.
      */
-    public static String hexDump(ByteBuffer buf)
+    public static String hexDump(final ByteBuffer buf)
     {
-        StringBuilder builder = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         int count = 0;
-        for(int p = buf.position(); p < buf.position() + buf.remaining(); p++)
+        for (int p = buf.position(); p < buf.position() + buf.remaining(); p++)
         {
             if (count % 16 == 0)
             {
@@ -414,15 +458,80 @@ public final class Strings
         return builder.toString();
     }
 
-    private static class StringSubstitutionResolver implements Resolver
+    /**
+     * Creates substitution resolver
+     *
+     * @param prefix Substitution prefix
+     * @param substitutions Map of substituitions
+     *
+     * @return StringSubstitutionResolver
+     */
+    public static Resolver createSubstitutionResolver(final String prefix, 
final LinkedHashMap<String, String> substitutions)
+    {
+        return new StringSubstitutionResolver(prefix, substitutions);
+    }
+
+    /**
+     * Resolver variable using supplied resolver
+     */
+    public interface Resolver
     {
+        String resolve(final String variable, final Resolver resolver);
+    }
 
-        private final ThreadLocal<Set<String>> _stack = new ThreadLocal<>();
+    /**
+     * Resolves variable from a map.
+     */
+    public static class MapResolver implements Resolver
+    {
+        private final Map<String,String> map;
+
+        public MapResolver(final Map<String,String> map)
+        {
+            this.map = map;
+        }
 
+        @Override
+        public String resolve(final String variable, final Resolver resolver)
+        {
+            return map.get(variable);
+        }
+    }
+
+    /**
+     * Chains two resolvers trying to resolve variable against first one and 
if unsuccessful against second one
+     */
+    public static class ChainedResolver implements Resolver
+    {
+        private final Resolver primary;
+        private final Resolver secondary;
+
+        public ChainedResolver(final Resolver primary, final Resolver 
secondary)
+        {
+            this.primary = primary;
+            this.secondary = secondary;
+        }
+
+        @Override
+        public String resolve(final String variable, final Resolver resolver)
+        {
+            final String result = primary.resolve(variable, resolver);
+            return result != null
+                    ? result
+                    : secondary.resolve(variable, resolver);
+        }
+    }
+
+    /**
+     * Resolves substituted variables
+     */
+    private static class StringSubstitutionResolver implements Resolver
+    {
+        private final ThreadLocal<Set<String>> _stack = new ThreadLocal<>();
         private final LinkedHashMap<String, String> _substitutions;
         private final String _prefix;
 
-        private StringSubstitutionResolver(String prefix, 
LinkedHashMap<String, String> substitutions)
+        private StringSubstitutionResolver(final String prefix, final 
LinkedHashMap<String, String> substitutions)
         {
             _prefix = prefix;
             _substitutions = substitutions;
@@ -433,22 +542,18 @@ public final class Strings
         {
             boolean clearStack = false;
             Set<String> currentStack = _stack.get();
-            if(currentStack == null)
+            if (currentStack == null)
             {
                 currentStack = new HashSet<>();
                 _stack.set(currentStack);
                 clearStack = true;
             }
-
             try
             {
-                if(currentStack.contains(variable))
+                if (currentStack.contains(variable))
                 {
                     throw new IllegalArgumentException("The value of attribute 
" + variable + " is defined recursively");
-
                 }
-
-
                 if (variable.startsWith(_prefix))
                 {
                     currentStack.add(variable);
@@ -457,9 +562,9 @@ public final class Strings
                     String expanded = Strings.expand("${" + 
variable.substring(_prefix.length()) + "}", resolver,
                                                      stack, false);
                     currentStack.remove(variable);
-                    if(expanded != null)
+                    if (expanded != null)
                     {
-                        for(Map.Entry<String,String> entry : 
_substitutions.entrySet())
+                        for (final Map.Entry<String,String> entry : 
_substitutions.entrySet())
                         {
                             expanded = expanded.replace(entry.getKey(), 
entry.getValue());
                         }
@@ -474,8 +579,7 @@ public final class Strings
             }
             finally
             {
-
-                if(clearStack)
+                if (clearStack)
                 {
                     _stack.remove();
                 }
diff --git 
a/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java 
b/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
index de0b88c4da..33bab79c41 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
@@ -26,6 +26,10 @@ import static org.junit.Assert.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -100,4 +104,82 @@ public class StringsTest extends UnitTestBase
         String actual = 
Strings.hexDump(ByteBuffer.wrap("12345678123456789".getBytes()));
         assertThat(actual, is(equalTo(expected)));
     }
+
+    @Test
+    public void toCharSequence()
+    {
+        CharSequence expected = "null";
+        CharSequence actual = Strings.toCharSequence(null).toString();
+        assertThat(expected, is(equalTo(actual)));
+
+        expected = "";
+        actual = Strings.toCharSequence("").toString();
+        assertThat(expected, is(equalTo(actual)));
+
+        Object object = new Object();
+        expected = object.toString();
+        actual = Strings.toCharSequence(object).toString();
+        assertThat(expected, is(equalTo(actual)));
+    }
+
+    @Test
+    public void decodeCharArray()
+    {
+        assertThat(null, is(equalTo(Strings.decodeCharArray(null, null))));
+        assertThat(new byte[]{}, is(equalTo(Strings.decodeCharArray(new 
char[]{}, null))));
+        assertThat(new byte[]{}, 
is(equalTo(Strings.decodeCharArray("".toCharArray(), null))));
+
+        final char[] base64 = 
Base64.getEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)).toCharArray();
+        assertThat(new byte[]{116, 101, 115, 116}, 
is(equalTo(Strings.decodeCharArray(base64, null))));
+    }
+
+    @Test
+    public void split()
+    {
+        assertThat(Collections.emptyList(), is(equalTo(Strings.split(null))));
+        assertThat(Collections.emptyList(), is(equalTo(Strings.split(""))));
+        assertThat(Collections.singletonList("a"), 
is(equalTo(Strings.split("a"))));
+        assertThat(Collections.singletonList("a "), 
is(equalTo(Strings.split("a "))));
+        assertThat(Arrays.asList("a", "b"), is(equalTo(Strings.split("a,b"))));
+    }
+
+    @Test
+    public void join()
+    {
+        try
+        {
+            Strings.join(",", (Object[]) null);
+        }
+        catch (NullPointerException e)
+        {
+            assertThat("Items must be not null", is(equalTo(e.getMessage())));
+        }
+
+        try
+        {
+            Strings.join(",", (Iterable<?>) null);
+        }
+        catch (NullPointerException e)
+        {
+            assertThat("Items must be not null", is(equalTo(e.getMessage())));
+        }
+
+        try
+        {
+            Strings.join(null, (Iterable<?>) null);
+        }
+        catch (NullPointerException e)
+        {
+            assertThat("Separator must be not null", 
is(equalTo(e.getMessage())));
+        }
+
+        assertThat("", is(equalTo(Strings.join(",", new Object[]{}))));
+        assertThat("", is(equalTo(Strings.join(",", new ArrayList<>()))));
+
+        assertThat("a,b,c", is(equalTo(Strings.join(",", new Object[]{"a", 
"b", "c"}))));
+        assertThat("a,b,c", is(equalTo(Strings.join(",", Arrays.asList("a", 
"b", "c")))));
+
+        assertThat("a,null,1", is(equalTo(Strings.join(",", new Object[]{"a", 
null, 1}))));
+        assertThat("a,null,1", is(equalTo(Strings.join(",", Arrays.asList("a", 
null, 1)))));
+    }
 }
diff --git 
a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
 
b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
index c79d4bde3d..cd87c21146 100644
--- 
a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
+++ 
b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
@@ -57,28 +57,33 @@ public class BasicAuthPreemptiveAuthenticator implements 
HttpRequestPreemptiveAu
             final String[] tokens = header.split("\\s");
             if (tokens.length >= 2 && "BASIC".equalsIgnoreCase(tokens[0]))
             {
-                boolean isBasicAuthSupported = false;
-                if (request.isSecure())
-                {
-                    isBasicAuthSupported = 
managementConfiguration.isHttpsBasicAuthenticationEnabled();
-                }
-                else
-                {
-                    isBasicAuthSupported = 
managementConfiguration.isHttpBasicAuthenticationEnabled();
-                }
+                boolean isBasicAuthSupported = request.isSecure()
+                    ? 
managementConfiguration.isHttpsBasicAuthenticationEnabled()
+                    : 
managementConfiguration.isHttpBasicAuthenticationEnabled();
                 if (isBasicAuthSupported)
                 {
-                    final String base64UsernameAndPassword = tokens[1];
-                    final String[] credentials = new 
String(Strings.decodePrivateBase64(base64UsernameAndPassword,
-                            "basic authentication credentials"), 
StandardCharsets.UTF_8).split(":", 2);
-                    if (credentials.length == 2)
+                    final byte[] base64EncodedContent = 
Strings.decodeCharArray(
+                        tokens[1].toCharArray(),
+                        "basic authentication credentials"
+                    );
+                    try
                     {
-                        final String username = credentials[0];
-                        final String password = credentials[1];
-                        final AuthenticationResult authenticationResult = 
namePasswdAuthProvider.authenticate(username, password);
-                        final SubjectAuthenticationResult result = 
subjectCreator.createResultWithGroups(authenticationResult);
-
-                        return result.getSubject();
+                        final String[] decodedHeaderContent =
+                                new String(base64EncodedContent, 
StandardCharsets.UTF_8).split(":", 2);
+                        if (decodedHeaderContent.length == 2)
+                        {
+                            final String token1 = decodedHeaderContent[0];
+                            final String token2 = decodedHeaderContent[1];
+                            final AuthenticationResult authenticationResult =
+                                    
namePasswdAuthProvider.authenticate(token1, token2);
+                            final SubjectAuthenticationResult result =
+                                    
subjectCreator.createResultWithGroups(authenticationResult);
+                            return result.getSubject();
+                        }
+                    }
+                    finally
+                    {
+                        Strings.clearByteArray(base64EncodedContent);
                     }
                 }
             }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to