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

davsclaus pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new 6d0751b1a7ae CAMEL-24650, CAMEL-24652: camel-http-common, 
camel-platform-http-vertx - match fileNameExtWhitelist entries exactly (#27268)
6d0751b1a7ae is described below

commit 6d0751b1a7ae9f5bffb4365640fb414960d0fc00
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Oct 2 11:35:16 2026 +0200

    CAMEL-24650, CAMEL-24652: camel-http-common, camel-platform-http-vertx - 
match fileNameExtWhitelist entries exactly (#27268)
    
    Backport of #26872 and #27194 to camel-4.18.x. fileNameExtWhitelist entries 
are now compared exactly and case-insensitively through the shared 
HttpHelper.isFileNameExtWhitelisted check, in VertxPlatformHttpConsumer and 
DefaultHttpBinding.populateAttachments, instead of being matched as a 
substring. The configured value is no longer modified.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../org/apache/camel/http/base/HttpHelper.java     | 35 +++++++++
 .../base/HttpHelperFileNameExtWhitelistTest.java   | 84 ++++++++++++++++++++++
 .../camel/http/common/DefaultHttpBinding.java      | 31 ++++----
 .../camel/http/common/DefaultHttpBindingTest.java  | 67 +++++++++++++++++
 .../component/jetty12/AttachmentHttpBinding.java   | 26 -------
 components/camel-platform-http-vertx/pom.xml       |  4 ++
 .../http/vertx/VertxPlatformHttpConsumer.java      | 18 ++---
 .../component/servlet/AttachmentHttpBinding.java   | 23 +-----
 8 files changed, 215 insertions(+), 73 deletions(-)

diff --git 
a/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
 
b/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
index 1a9198796eb1..7bfa7ad396c9 100644
--- 
a/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
+++ 
b/components/camel-http-base/src/main/java/org/apache/camel/http/base/HttpHelper.java
@@ -19,6 +19,7 @@ package org.apache.camel.http.base;
 import java.net.ProtocolException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.function.BiConsumer;
 
@@ -26,6 +27,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.support.http.HttpUtil;
 import org.apache.camel.util.CollectionHelper;
+import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
 
@@ -175,4 +177,37 @@ public final class HttpHelper {
         }
     }
 
+    /**
+     * Whether an uploaded file is accepted according to a {@code 
fileNameExtWhitelist}.
+     * <p/>
+     * The file name extension is compared, case-insensitively, against each 
comma-separated entry of the whitelist
+     * exactly and not as a substring: a whitelist of {@code txt} must not 
accept an upload named {@code evil.x} just
+     * because {@code "txt".contains("x")}. A file is accepted when no 
whitelist is configured, when the whitelist is
+     * {@code *}, or when the file name has no extension. The configured 
whitelist value is not modified.
+     *
+     * @param  whitelist the configured {@code fileNameExtWhitelist} (may be 
{@code null})
+     * @param  fileName  the file name submitted by the client
+     * @return           true if the file is accepted
+     */
+    public static boolean isFileNameExtWhitelisted(String whitelist, String 
fileName) {
+        if (whitelist == null) {
+            return true;
+        }
+        String ext = FileUtil.onlyExt(fileName);
+        if (ext == null) {
+            return true;
+        }
+        ext = ext.toLowerCase(Locale.US);
+        String lcWhitelist = whitelist.toLowerCase(Locale.US);
+        if (lcWhitelist.equals("*")) {
+            return true;
+        }
+        for (String allowed : lcWhitelist.split(",")) {
+            if (allowed.trim().equals(ext)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }
diff --git 
a/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperFileNameExtWhitelistTest.java
 
b/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperFileNameExtWhitelistTest.java
new file mode 100644
index 000000000000..b6ab8eec7eed
--- /dev/null
+++ 
b/components/camel-http-base/src/test/java/org/apache/camel/http/base/HttpHelperFileNameExtWhitelistTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.http.base;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for the shared {@code fileNameExtWhitelist} check used by the HTTP 
bindings and the platform-http-vertx
+ * consumer (CAMEL-24652). The important property is that an extension is 
matched as a whole comma-separated token and
+ * never as a substring of the whitelist.
+ */
+class HttpHelperFileNameExtWhitelistTest {
+
+    @Test
+    void nullWhitelistAcceptsEverything() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted(null, "evil.exe"));
+    }
+
+    @Test
+    void starWhitelistAcceptsEverything() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("*", "evil.exe"));
+    }
+
+    @Test
+    void aNameWithoutAnExtensionIsAccepted() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("txt", "noextension"));
+    }
+
+    @Test
+    void anExactExtensionIsAccepted() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("txt", "notes.txt"));
+    }
+
+    @Test
+    void aSubstringOfTheWhitelistIsNotAccepted() {
+        // the bug this guards: "txt".contains("x") must not accept "evil.x"
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("txt", "evil.x"));
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("txt", "evil.t"));
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("txt", "evil.tx"));
+    }
+
+    @Test
+    void commaSeparatedTokensAreMatchedExactlyAndTrimmed() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("txt,pdf", 
"report.pdf"));
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("txt, pdf", 
"report.pdf"));
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("txt,pdf", 
"report.doc"));
+    }
+
+    @Test
+    void theMatchIsCaseInsensitive() {
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("TXT", "notes.txt"));
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("txt", "NOTES.TXT"));
+    }
+
+    @Test
+    void aMultiDotNameIsMatchedOnItsFullExtensionChain() {
+        // FileUtil.onlyExt returns everything after the first dot, so the 
whole chain must be whitelisted
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("gz", 
"archive.tar.gz"));
+        assertTrue(HttpHelper.isFileNameExtWhitelisted("tar.gz", 
"archive.tar.gz"));
+    }
+
+    @Test
+    void aDoubleExtensionIsNotAcceptedOnItsTrailingExtension() {
+        // evil.php.jpg must not pass a "jpg" whitelist: its extension chain 
is "php.jpg", not "jpg"
+        assertFalse(HttpHelper.isFileNameExtWhitelisted("jpg", 
"evil.php.jpg"));
+    }
+}
diff --git 
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java
 
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java
index 797f83c3bd05..63c4cb50170b 100644
--- 
a/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java
+++ 
b/components/camel-http-common/src/main/java/org/apache/camel/http/common/DefaultHttpBinding.java
@@ -55,7 +55,6 @@ import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.GZIPHelper;
 import org.apache.camel.support.MessageHelper;
 import org.apache.camel.support.ObjectHelper;
-import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -335,17 +334,7 @@ public class DefaultHttpBinding implements HttpBinding {
                     fileName = fileName.replaceAll("[\n\r\t]", "_");
                 }
                 // is the file name accepted
-                boolean accepted = true;
-                if (fileNameExtWhitelist != null) {
-                    String ext = FileUtil.onlyExt(fileName);
-                    if (ext != null) {
-                        ext = ext.toLowerCase(Locale.US);
-                        fileNameExtWhitelist = 
fileNameExtWhitelist.toLowerCase(Locale.US);
-                        if (!fileNameExtWhitelist.equals("*") && 
!fileNameExtWhitelist.contains(ext)) {
-                            accepted = false;
-                        }
-                    }
-                }
+                boolean accepted = isFileNameAccepted(fileName);
                 if (accepted) {
                     AttachmentMessage am = 
message.getExchange().getMessage(AttachmentMessage.class);
                     am.addAttachment(fileName, new DataHandler(new 
CamelFileDataSource(fileObject, fileName)));
@@ -358,6 +347,24 @@ public class DefaultHttpBinding implements HttpBinding {
         }
     }
 
+    /**
+     * Whether an uploaded file is accepted according to the configured {@link 
#getFileNameExtWhitelist()}.
+     * <p/>
+     * The file name extension is compared, case-insensitively, against each 
comma-separated entry of the whitelist
+     * exactly and not as a substring: a whitelist of "txt" must not accept an 
upload named "evil.x" just because
+     * "txt".contains("x"). A file is accepted when no whitelist is 
configured, when the whitelist is "*", or when the
+     * file name has no extension.
+     *
+     * @param  fileName the file name submitted by the client
+     * @return          true if the file is accepted
+     */
+    protected boolean isFileNameAccepted(String fileName) {
+        // one shared implementation of the whitelist check lives in 
camel-http-base's HttpHelper so the servlet and
+        // jetty bindings here and the camel-platform-http-vertx consumer 
cannot drift apart again (CAMEL-24652).
+        // Fully qualified because this package has its own HttpHelper with 
the same simple name.
+        return 
org.apache.camel.http.base.HttpHelper.isFileNameExtWhitelisted(getFileNameExtWhitelist(),
 fileName);
+    }
+
     @Override
     public void writeResponse(Exchange exchange, HttpServletResponse response) 
throws IOException {
         Message target = exchange.getMessage();
diff --git 
a/components/camel-http-common/src/test/java/org/apache/camel/http/common/DefaultHttpBindingTest.java
 
b/components/camel-http-common/src/test/java/org/apache/camel/http/common/DefaultHttpBindingTest.java
index f535d5ea7cdd..898b860985f8 100644
--- 
a/components/camel-http-common/src/test/java/org/apache/camel/http/common/DefaultHttpBindingTest.java
+++ 
b/components/camel-http-common/src/test/java/org/apache/camel/http/common/DefaultHttpBindingTest.java
@@ -24,7 +24,9 @@ import org.apache.camel.test.junit5.CamelTestSupport;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class DefaultHttpBindingTest extends CamelTestSupport {
 
@@ -69,4 +71,69 @@ public class DefaultHttpBindingTest extends CamelTestSupport 
{
         String value = binding.convertHeaderValueToString(exchange, l);
         assertEquals(value, l.toString());
     }
+
+    @Test
+    public void testFileNameAcceptedWithoutWhitelist() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+
+        assertTrue(binding.isFileNameAccepted("report.pdf"));
+    }
+
+    @Test
+    public void testFileNameAcceptedWithoutExtension() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("txt");
+
+        assertTrue(binding.isFileNameAccepted("README"));
+        assertTrue(binding.isFileNameAccepted(null));
+    }
+
+    @Test
+    public void testFileNameAcceptedWithWildcardWhitelist() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("*");
+
+        assertTrue(binding.isFileNameAccepted("report.pdf"));
+        assertTrue(binding.isFileNameAccepted("script.sh"));
+    }
+
+    @Test
+    public void testFileNameAcceptedMatchesEachExtension() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("txt, pdf");
+
+        assertTrue(binding.isFileNameAccepted("notes.txt"));
+        assertTrue(binding.isFileNameAccepted("report.pdf"));
+        assertFalse(binding.isFileNameAccepted("image.png"));
+    }
+
+    @Test
+    public void testFileNameAcceptedIsCaseInsensitive() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("TXT,pdf");
+
+        assertTrue(binding.isFileNameAccepted("notes.txt"));
+        assertTrue(binding.isFileNameAccepted("REPORT.PDF"));
+    }
+
+    @Test
+    public void testFileNameAcceptedDoesNotMatchSubstring() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("txt");
+
+        // "txt".contains("x") must not let an upload named evil.x through
+        assertFalse(binding.isFileNameAccepted("evil.x"));
+
+        binding.setFileNameExtWhitelist("txtdoc");
+        assertFalse(binding.isFileNameAccepted("notes.txt"));
+    }
+
+    @Test
+    public void testFileNameAcceptedDoesNotChangeConfiguredWhitelist() {
+        DefaultHttpBinding binding = new DefaultHttpBinding();
+        binding.setFileNameExtWhitelist("TXT");
+
+        binding.isFileNameAccepted("notes.txt");
+        assertEquals("TXT", binding.getFileNameExtWhitelist());
+    }
 }
diff --git 
a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java
 
b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java
index 872a6615ff1d..15d0afc084f4 100644
--- 
a/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java
+++ 
b/components/camel-jetty/src/main/java/org/apache/camel/component/jetty12/AttachmentHttpBinding.java
@@ -21,7 +21,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Collection;
 import java.util.Enumeration;
-import java.util.Locale;
 import java.util.Map;
 
 import jakarta.activation.DataHandler;
@@ -38,7 +37,6 @@ import org.apache.camel.attachment.DefaultAttachmentMessage;
 import org.apache.camel.component.jetty.MultiPartFilter;
 import org.apache.camel.http.common.DefaultHttpBinding;
 import org.apache.camel.http.common.HttpHelper;
-import org.apache.camel.util.FileUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -101,30 +99,6 @@ final class AttachmentHttpBinding extends 
DefaultHttpBinding {
         }
     }
 
-    private boolean isFileNameAccepted(String fileName) {
-        String whitelist = getFileNameExtWhitelist();
-        if (whitelist == null) {
-            return true;
-        }
-        String ext = FileUtil.onlyExt(fileName);
-        if (ext == null) {
-            return true;
-        }
-        ext = ext.toLowerCase(Locale.US);
-        whitelist = whitelist.toLowerCase(Locale.US);
-        if (whitelist.equals("*")) {
-            return true;
-        }
-        // compare against each comma-separated extension exactly, not as a 
substring: a whitelist of "txt"
-        // must not accept an upload named "evil.x" just because 
"txt".contains("x")
-        for (String allowed : whitelist.split(",")) {
-            if (allowed.trim().equals(ext)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     @Override
     protected void populateRequestParameters(HttpServletRequest request, 
Message message) {
         // we populate the http request parameters without checking the request
diff --git a/components/camel-platform-http-vertx/pom.xml 
b/components/camel-platform-http-vertx/pom.xml
index 68772f17063c..5acd0c5f1d13 100644
--- a/components/camel-platform-http-vertx/pom.xml
+++ b/components/camel-platform-http-vertx/pom.xml
@@ -42,6 +42,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-platform-http</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-http-base</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-attachments</artifactId>
diff --git 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
index d5a248039cd4..e48193f9c779 100644
--- 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
+++ 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
@@ -53,11 +53,11 @@ import 
org.apache.camel.component.platform.http.cookie.CookieConfiguration;
 import org.apache.camel.component.platform.http.cookie.CookieHandler;
 import org.apache.camel.component.platform.http.spi.Method;
 import org.apache.camel.component.platform.http.spi.PlatformHttpConsumer;
+import org.apache.camel.http.base.HttpHelper;
 import org.apache.camel.spi.HeaderFilterStrategy;
 import org.apache.camel.spi.RestRegistry;
 import org.apache.camel.support.DefaultConsumer;
 import org.apache.camel.support.PluginHelper;
-import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.MimeTypeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -449,18 +449,10 @@ public class VertxPlatformHttpConsumer extends 
DefaultConsumer
 
             LOGGER.trace("HTTP attachment {} = {}", name, fileName);
 
-            // is the file name accepted
-            boolean accepted = true;
-
-            if (fileNameExtWhitelist != null) {
-                String ext = FileUtil.onlyExt(fileName);
-                if (ext != null) {
-                    ext = ext.toLowerCase(Locale.US);
-                    if (!fileNameExtWhitelist.equals("*") && 
!fileNameExtWhitelist.contains(ext)) {
-                        accepted = false;
-                    }
-                }
-            }
+            // is the file name accepted - shared with the http bindings so 
the whitelist matches whole
+            // comma-separated extension tokens, not a substring: a whitelist 
of "txt" must not accept an upload
+            // named "evil.x" just because "txt".contains("x") (CAMEL-24652)
+            boolean accepted = 
HttpHelper.isFileNameExtWhitelisted(fileNameExtWhitelist, fileName);
             if (accepted) {
                 final File localFile = new File(upload.uploadedFileName());
                 final AttachmentMessage attachmentMessage = 
message.getExchange().getMessage(AttachmentMessage.class);
diff --git 
a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
 
b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
index aeb78820d1b2..e60c8f94219f 100644
--- 
a/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
+++ 
b/components/camel-servlet/src/main/java/org/apache/camel/component/servlet/AttachmentHttpBinding.java
@@ -20,7 +20,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Collection;
-import java.util.Locale;
 
 import jakarta.activation.DataSource;
 import jakarta.servlet.http.HttpServletRequest;
@@ -34,7 +33,6 @@ import org.apache.camel.attachment.DefaultAttachment;
 import org.apache.camel.attachment.DefaultAttachmentMessage;
 import org.apache.camel.http.common.DefaultHttpBinding;
 import org.apache.camel.http.common.HttpHelper;
-import org.apache.camel.util.FileUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,15 +57,7 @@ public final class AttachmentHttpBinding extends 
DefaultHttpBinding {
                 // name and not against Part.getName(), which is the multipart 
field name
                 String fileName = part.getSubmittedFileName();
                 // is the file name accepted
-                boolean accepted = true;
-                if (getFileNameExtWhitelist() != null) {
-                    String ext = FileUtil.onlyExt(fileName);
-                    if (ext != null) {
-                        ext = ext.toLowerCase(Locale.US);
-                        String whiteList = 
getFileNameExtWhitelist().toLowerCase(Locale.US);
-                        accepted = whiteList.equals("*") || 
isExtWhitelisted(whiteList, ext);
-                    }
-                }
+                boolean accepted = isFileNameAccepted(fileName);
 
                 if (accepted) {
                     DataSource ds = new PartDataSource(part);
@@ -90,17 +80,6 @@ public final class AttachmentHttpBinding extends 
DefaultHttpBinding {
         }
     }
 
-    // compare against each comma-separated extension exactly, not as a 
substring: a whitelist of "txt"
-    // must not accept an upload named "evil.x" just because 
"txt".contains("x")
-    private static boolean isExtWhitelisted(String whitelist, String ext) {
-        for (String allowed : whitelist.split(",")) {
-            if (allowed.trim().equals(ext)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public final class PartDataSource implements DataSource {
         private final Part part;
 

Reply via email to