492769: OsgiFilterUtils should escape filter values that come from ServiceReference properties

Signed-off-by: Olaf Otto <olaf@x100.de>
diff --git a/core/src/main/java/org/eclipse/gemini/blueprint/util/OsgiFilterUtils.java b/core/src/main/java/org/eclipse/gemini/blueprint/util/OsgiFilterUtils.java
index ab07ec9..0c16470 100644
--- a/core/src/main/java/org/eclipse/gemini/blueprint/util/OsgiFilterUtils.java
+++ b/core/src/main/java/org/eclipse/gemini/blueprint/util/OsgiFilterUtils.java
@@ -14,17 +14,17 @@
 

 package org.eclipse.gemini.blueprint.util;

 

-import java.util.Collection;

-

-import org.osgi.framework.Constants;

-import org.osgi.framework.Filter;

-import org.osgi.framework.FrameworkUtil;

-import org.osgi.framework.InvalidSyntaxException;

-import org.osgi.framework.ServiceReference;

+import org.osgi.framework.*;

 import org.springframework.util.Assert;

 import org.springframework.util.ObjectUtils;

 import org.springframework.util.StringUtils;

 

+import java.nio.CharBuffer;

+import java.util.Collection;

+

+import static java.lang.String.valueOf;

+import static java.nio.CharBuffer.allocate;

+

 /**

  * Utility class for creating OSGi filters. This class allows filter creation and concatenation from common parameters

  * such as class names.

@@ -32,7 +32,6 @@
  * @author Costin Leau

  */

 public abstract class OsgiFilterUtils {

-

 	private static final char FILTER_BEGIN = '(';

 

 	private static final char FILTER_END = ')';

@@ -227,9 +226,6 @@
 

 	/**

 	 * Creates a filter (as String) that matches the properties (expect the service id) of service reference.

-	 * 

-	 * @param reference

-	 * @return

 	 */

 	public static String getFilter(ServiceReference reference) {

 		String[] propertyKeys = reference.getPropertyKeys();

@@ -240,15 +236,15 @@
 			if (!Constants.SERVICE_ID.equals(key)) {

 				Object value = reference.getProperty(key);

 				Class<?> cl = value.getClass();

-				Iterable it;

+

 				// array

 				if (cl.isArray()) {

 					Object[] array = ObjectUtils.toObjectArray(value);

 					for (Object item : array) {

 						sb.append("(");

-						sb.append(key);

+						sb.append(escapeFilterCharacters(key));

 						sb.append("=");

-						sb.append(item);

+						sb.append(escapeFilterCharacters(valueOf(item)));

 						sb.append(")");

 					}

 				}

@@ -258,9 +254,9 @@
 					Collection<?> c = (Collection) value;

 					for (Object item : c) {

 						sb.append("(");

-						sb.append(key);

+						sb.append(escapeFilterCharacters(key));

 						sb.append("=");

-						sb.append(item);

+						sb.append(escapeFilterCharacters(valueOf(item)));

 						sb.append(")");

 					}

 				}

@@ -268,9 +264,9 @@
 				// scalar/primitive

 				else {

 					sb.append("(");

-					sb.append(key);

+					sb.append(escapeFilterCharacters(key));

 					sb.append("=");

-					sb.append(value);

+					sb.append(escapeFilterCharacters(valueOf(value)));

 					sb.append(")");

 				}

 			}

@@ -279,4 +275,18 @@
 		sb.append(")");

 		return sb.toString();

 	}

+

+	private static String escapeFilterCharacters(String value) {

+		CharBuffer buffer = allocate(value.length() * 2);

+		for ( char c : value.toCharArray()) {

+			switch (c) {

+				case '*'  : buffer.append('\\'); break;

+				case '\\' : buffer.append('\\'); break;

+				case '('  : buffer.append('\\'); break;

+				case ')'  : buffer.append('\\'); break;

+			}

+			buffer.append(c);

+		}

+		return buffer.flip().toString();

+	}

 }
\ No newline at end of file
diff --git a/core/src/test/java/org/eclipse/gemini/blueprint/service/OsgiFilterUtilsTest.java b/core/src/test/java/org/eclipse/gemini/blueprint/service/OsgiFilterUtilsTest.java
index eed1fe5..1cd64f8 100644
--- a/core/src/test/java/org/eclipse/gemini/blueprint/service/OsgiFilterUtilsTest.java
+++ b/core/src/test/java/org/eclipse/gemini/blueprint/service/OsgiFilterUtilsTest.java
@@ -20,7 +20,9 @@
 

 import junit.framework.TestCase;

 

+import org.eclipse.gemini.blueprint.mock.MockServiceReference;

 import org.eclipse.gemini.blueprint.util.OsgiFilterUtils;

+import org.fest.assertions.Assertions;

 import org.osgi.framework.BundleContext;

 import org.osgi.framework.Constants;

 import org.osgi.framework.Filter;

@@ -174,6 +176,23 @@
 

 	}

 

+	/**

+	 * As per OSGI r5 spec, https://www.scribd.com/document/137122057/osgi-core-5-0-0, the characters

+	 * <code> \ * ( )</code>  must be escaped using a \ character.

+	 */

+	public void testFiltersFromServiceReferencesAreEscaped() {

+		MockServiceReference serviceReference = new MockServiceReference();

+		Dictionary<String, String> properties = new Hashtable<>();

+		properties.put("ds.target", "(thing=ball)");

+		properties.put("all.escapable", "*()\\");

+		serviceReference.setProperties(properties);

+

+		String actual = OsgiFilterUtils.getFilter(serviceReference);

+

+		Assertions.assertThat(actual).contains("(ds.target=\\(thing=ball\\)");

+		Assertions.assertThat(actual).contains("\\*\\(\\)\\");

+	}

+

     protected BundleContext getBundleContext() {

         return new MockBundleContext() {

             public Filter createFilter(String filter) throws InvalidSyntaxException {