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

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karaf.git


The following commit(s) were added to refs/heads/main by this push:
     new cc7dbb9cc fix(#729): escape names embedded in OSGi service filters 
(#738)
cc7dbb9cc is described below

commit cc7dbb9cc8b777a909d1907179e65f89d00ef40f
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Aug 27 17:53:04 2026 +0200

    fix(#729): escape names embedded in OSGi service filters (#738)
    
    Six lookups in camel-core-osgi built a service filter by concatenating a
    name straight into it: OsgiBeanRepository (name= and service.pid=),
    OsgiComponentResolver (component=), OsgiLanguageResolver (language= and
    resolver=) and OsgiDataFormatResolver (dataformat=).
    
    Filter metacharacters in the name were therefore read as filter syntax
    rather than matched as text. A name of "*" becomes a presence assertion
    matching every registered service, and since each site takes refs[0] the
    lookup returned an arbitrary service instead of not resolving. A name
    containing ")(" made the framework reject the filter, faulting the
    exchange with a syntax error instead of a clean not-found.
    
    Route this through OsgiFilterHelper.createFilter, which escapes the value
    first. Escaping only: the FQCN and service.pid interpretations in
    lookupByName are the documented purpose of the class and are left alone,
    since narrowing them would change behaviour for existing deployments.
    getServiceReference(name) at OsgiBeanRepository:80 takes an exact
    interface name rather than a filter, so it needs nothing.
    
    Note the escaping is the OSGi filter grammar's backslash-before-character
    form, not the RFC 4515 "\2a" hex form - an OSGi Filter reads the latter as
    the two literal characters 2a. The tests caught this: they assert against
    a real FrameworkUtil.createFilter, so they check the filter actually stops
    matching rather than just that a string was rewritten.
---
 core/camel-core-osgi/pom.xml                       |   6 ++
 .../camel/karaf/core/OsgiBeanRepository.java       |   5 +-
 .../camel/karaf/core/OsgiComponentResolver.java    |   3 +-
 .../camel/karaf/core/OsgiDataFormatResolver.java   |   3 +-
 .../camel/karaf/core/OsgiLanguageResolver.java     |   5 +-
 .../camel/karaf/core/utils/OsgiFilterHelper.java   |  87 ++++++++++++++++++
 .../karaf/core/utils/OsgiFilterHelperTest.java     | 102 +++++++++++++++++++++
 7 files changed, 205 insertions(+), 6 deletions(-)

diff --git a/core/camel-core-osgi/pom.xml b/core/camel-core-osgi/pom.xml
index 0863bb710..616abaf24 100644
--- a/core/camel-core-osgi/pom.xml
+++ b/core/camel-core-osgi/pom.xml
@@ -73,6 +73,12 @@
             <version>${junit-jupiter-version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${junit-jupiter-version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiBeanRepository.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiBeanRepository.java
index 99efc154a..31fcc4ec9 100644
--- 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiBeanRepository.java
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiBeanRepository.java
@@ -27,6 +27,7 @@ import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.Service;
 import org.apache.camel.spi.BeanRepository;
 import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.karaf.core.utils.OsgiFilterHelper;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
@@ -57,7 +58,7 @@ public class OsgiBeanRepository extends 
LifecycleStrategySupport implements Bean
         Object service = null;
         ServiceReference<?> sr;
         try {
-            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(type.getName(), "(name=" + name + ")");
+            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(type.getName(), 
OsgiFilterHelper.createFilter("name", name));
             if (refs != null && refs.length > 0) {
                 // just return the first one
                 sr = refs[0];
@@ -80,7 +81,7 @@ public class OsgiBeanRepository extends 
LifecycleStrategySupport implements Bean
         ServiceReference<?> sr = bundleContext.getServiceReference(name);
         if (sr == null) {
             // trying to lookup service by PID if not found by name
-            String filterExpression = "(" + Constants.SERVICE_PID + "=" + name 
+ ")";
+            String filterExpression = 
OsgiFilterHelper.createFilter(Constants.SERVICE_PID, name);
             try {
                 ServiceReference<?>[] refs = 
bundleContext.getServiceReferences((String)null, filterExpression);
                 if (refs != null && refs.length > 0) {
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiComponentResolver.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiComponentResolver.java
index a80fbdab7..0e2741b35 100644
--- 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiComponentResolver.java
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiComponentResolver.java
@@ -21,6 +21,7 @@ import org.apache.camel.Component;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.spi.ComponentResolver;
 import org.apache.camel.support.ResolverHelper;
+import org.apache.camel.karaf.core.utils.OsgiFilterHelper;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -50,7 +51,7 @@ public class OsgiComponentResolver implements 
ComponentResolver {
     protected Component getComponent(String name, CamelContext context) throws 
Exception {
         LOG.trace("Finding Component: {}", name);
         try {
-            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(ComponentResolver.class.getName(), 
"(component=" + name + ")");
+            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(ComponentResolver.class.getName(), 
OsgiFilterHelper.createFilter("component", name));
             if (refs != null) {
                 for (ServiceReference<?> ref : refs) {
                     Object service = bundleContext.getService(ref);
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiDataFormatResolver.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiDataFormatResolver.java
index 29d5689e6..c0c414e4f 100644
--- 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiDataFormatResolver.java
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiDataFormatResolver.java
@@ -24,6 +24,7 @@ import org.apache.camel.spi.DataFormat;
 import org.apache.camel.spi.DataFormatFactory;
 import org.apache.camel.spi.DataFormatResolver;
 import org.apache.camel.support.ResolverHelper;
+import org.apache.camel.karaf.core.utils.OsgiFilterHelper;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -59,7 +60,7 @@ public class OsgiDataFormatResolver implements 
DataFormatResolver {
     private DataFormat getDataFormat(String name, CamelContext context) {
         LOG.trace("Finding DataFormat: {}", name);
         try {
-            Collection<ServiceReference<DataFormatResolver>> refs = 
bundleContext.getServiceReferences(DataFormatResolver.class, "(dataformat=" + 
name + ")");
+            Collection<ServiceReference<DataFormatResolver>> refs = 
bundleContext.getServiceReferences(DataFormatResolver.class, 
OsgiFilterHelper.createFilter("dataformat", name));
             if (refs != null) {
                 for (ServiceReference<DataFormatResolver> ref : refs) {
                     return 
bundleContext.getService(ref).createDataFormat(name, context);
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiLanguageResolver.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiLanguageResolver.java
index fa2736e2a..36a7b8897 100644
--- 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiLanguageResolver.java
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/OsgiLanguageResolver.java
@@ -22,6 +22,7 @@ import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.spi.Language;
 import org.apache.camel.spi.LanguageResolver;
 import org.apache.camel.support.ResolverHelper;
+import org.apache.camel.karaf.core.utils.OsgiFilterHelper;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -59,7 +60,7 @@ public class OsgiLanguageResolver implements LanguageResolver 
{
     protected Language getLanguage(String name, CamelContext context) {
         LOG.trace("Finding Language: {}", name);
         try {
-            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(LanguageResolver.class.getName(), 
"(language=" + name + ")");
+            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(LanguageResolver.class.getName(), 
OsgiFilterHelper.createFilter("language", name));
             if (refs != null) {
                 for (ServiceReference<?> ref : refs) {
                     Object service = bundleContext.getService(ref);
@@ -79,7 +80,7 @@ public class OsgiLanguageResolver implements LanguageResolver 
{
     protected LanguageResolver getLanguageResolver(String name, CamelContext 
context) {
         LOG.trace("Finding LanguageResolver: {}", name);
         try {
-            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(LanguageResolver.class.getName(), 
"(resolver=" + name + ")");
+            ServiceReference<?>[] refs = 
bundleContext.getServiceReferences(LanguageResolver.class.getName(), 
OsgiFilterHelper.createFilter("resolver", name));
             if (refs != null) {
                 for (ServiceReference<?> ref : refs) {
                     Object service = bundleContext.getService(ref);
diff --git 
a/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/utils/OsgiFilterHelper.java
 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/utils/OsgiFilterHelper.java
new file mode 100644
index 000000000..eeb6d7ce3
--- /dev/null
+++ 
b/core/camel-core-osgi/src/main/java/org/apache/camel/karaf/core/utils/OsgiFilterHelper.java
@@ -0,0 +1,87 @@
+/*
+ * 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.camel.karaf.core.utils;
+
+/**
+ * Helper to build OSGi service filters from values that are not known to be 
filter safe.
+ */
+public final class OsgiFilterHelper {
+
+    private OsgiFilterHelper() {
+    }
+
+    /**
+     * Builds the filter <tt>(key=value)</tt>, escaping the value so it is 
matched literally.
+     *
+     * @param  key   the attribute to match on, must be a literal known to the 
caller
+     * @param  value the value to match, escaped before being embedded
+     * @return       the filter expression
+     */
+    public static String createFilter(String key, String value) {
+        return "(" + key + "=" + escapeFilterValue(value) + ")";
+    }
+
+    /**
+     * Escapes the characters that are significant in an OSGi filter value.
+     * <p/>
+     * The OSGi core specification defines its own filter grammar, in which a 
value escapes <tt>(</tt>, <tt>)</tt>,
+     * <tt>*</tt> and <tt>\</tt> by prefixing a single backslash. Note this is 
not the <tt>\2a</tt> hex form used by
+     * the LDAP string representation in RFC 4515: an OSGi {@code Filter} 
would read that as the two literal
+     * characters <tt>2a</tt>.
+     * <p/>
+     * Without escaping, a name is parsed as filter syntax rather than matched 
as text, so <tt>*</tt> becomes a
+     * presence assertion matching every registered service, and an unbalanced 
parenthesis makes the framework reject
+     * the filter instead of simply not matching.
+     *
+     * @param  value the value to escape, may be <tt>null</tt>
+     * @return       the escaped value, or <tt>null</tt> if the given value 
was <tt>null</tt>
+     */
+    public static String escapeFilterValue(String value) {
+        if (value == null) {
+            return null;
+        }
+        int first = indexOfSignificantCharacter(value);
+        if (first == -1) {
+            // legitimate component, language, dataformat and bean names never 
need escaping
+            return value;
+        }
+        StringBuilder sb = new StringBuilder(value.length() + 8);
+        sb.append(value, 0, first);
+        for (int i = first; i < value.length(); i++) {
+            char ch = value.charAt(i);
+            switch (ch) {
+                case '*', '(', ')', '\\' -> sb.append('\\').append(ch);
+                default -> sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    private static int indexOfSignificantCharacter(String value) {
+        for (int i = 0; i < value.length(); i++) {
+            switch (value.charAt(i)) {
+                case '*', '(', ')', '\\' -> {
+                    return i;
+                }
+                default -> {
+                    // keep looking
+                }
+            }
+        }
+        return -1;
+    }
+}
diff --git 
a/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/utils/OsgiFilterHelperTest.java
 
b/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/utils/OsgiFilterHelperTest.java
new file mode 100644
index 000000000..5edffaa60
--- /dev/null
+++ 
b/core/camel-core-osgi/src/test/java/org/apache/camel/karaf/core/utils/OsgiFilterHelperTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.camel.karaf.core.utils;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OsgiFilterHelperTest {
+
+    @Test
+    public void testNullValue() {
+        assertNull(OsgiFilterHelper.escapeFilterValue(null));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"jms", "http", "aws2-s3", "camel-bean", "xpath", 
"org.apache.camel.MyBean", "a+b"})
+    public void testLegitimateNamesAreUnchanged(String name) {
+        assertEquals(name, OsgiFilterHelper.escapeFilterValue(name));
+        assertEquals("(component=" + name + ")", 
OsgiFilterHelper.createFilter("component", name));
+    }
+
+    @Test
+    public void testSignificantCharactersAreEscaped() {
+        // the OSGi filter grammar escapes with a backslash before the 
character, not with the
+        // RFC 4515 hex form - an OSGi Filter reads "\\2a" as the two literal 
characters 2a
+        assertEquals("\\*", OsgiFilterHelper.escapeFilterValue("*"));
+        assertEquals("\\(", OsgiFilterHelper.escapeFilterValue("("));
+        assertEquals("\\)", OsgiFilterHelper.escapeFilterValue(")"));
+        assertEquals("\\\\", OsgiFilterHelper.escapeFilterValue("\\"));
+        assertEquals("a\\*b", OsgiFilterHelper.escapeFilterValue("a*b"));
+    }
+
+    /**
+     * The point of the escaping: a wildcard name must stop selecting every 
registered service.
+     */
+    @Test
+    public void testWildcardNoLongerMatchesAnArbitraryService() throws 
InvalidSyntaxException {
+        Filter filter = 
FrameworkUtil.createFilter(OsgiFilterHelper.createFilter("name", "*"));
+        assertFalse(filter.match(properties("name", "someRegisteredBean")),
+                "an escaped * must not match an unrelated service");
+        assertFalse(filter.match(properties("name", "anotherBean")),
+                "an escaped * must not match an unrelated service");
+        assertTrue(filter.match(properties("name", "*")),
+                "it must still match a service whose name really is *");
+    }
+
+    @Test
+    public void testUnescapedWildcardWouldHaveMatched() throws 
InvalidSyntaxException {
+        // documents the behaviour being fixed
+        Filter unescaped = FrameworkUtil.createFilter("(name=*)");
+        assertTrue(unescaped.match(properties("name", "someRegisteredBean")));
+    }
+
+    @Test
+    public void testInjectedFilterSyntaxIsNeutralised() throws 
InvalidSyntaxException {
+        String hostile = 
"x)(objectClass=org.apache.karaf.features.FeaturesService";
+        Filter filter = assertDoesNotThrow(() -> 
FrameworkUtil.createFilter(OsgiFilterHelper.createFilter("name", hostile)),
+                "an escaped name must produce a valid filter rather than a 
syntax error");
+        assertFalse(filter.match(properties("name", "someRegisteredBean")));
+        assertFalse(filter.match(properties("objectClass", 
"org.apache.karaf.features.FeaturesService")));
+        assertTrue(filter.match(properties("name", hostile)));
+    }
+
+    @Test
+    public void testUnbalancedParenthesisNoLongerFaultsTheFilter() {
+        assertDoesNotThrow(() -> 
FrameworkUtil.createFilter(OsgiFilterHelper.createFilter("component", ")(")),
+                "an unbalanced parenthesis must not make the framework reject 
the filter");
+    }
+
+    private static Dictionary<String, Object> properties(String key, Object 
value) {
+        Dictionary<String, Object> d = new Hashtable<>();
+        d.put(key, value);
+        return d;
+    }
+}

Reply via email to