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

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


The following commit(s) were added to refs/heads/main by this push:
     new efd80d5ee2c7 CAMEL-24652: camel-http-base, camel-http-common, 
camel-platform-http-vertx - one fileNameExtWhitelist check (#27194)
efd80d5ee2c7 is described below

commit efd80d5ee2c7461942b71c318a01cf798916a625
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Oct 1 12:55:54 2026 +0200

    CAMEL-24652: camel-http-base, camel-http-common, camel-platform-http-vertx 
- one fileNameExtWhitelist check (#27194)
    
    fileNameExtWhitelist had drifting copies of its check. DefaultHttpBinding 
(used
    by camel-servlet and camel-jetty) compared each comma-separated extension 
token
    exactly, while VertxPlatformHttpConsumer still used the loose
    fileNameExtWhitelist.contains(ext) substring form: with 
fileNameExtWhitelist=txt
    a platform-http-vertx upload named evil.x, evil.t or evil.tx was accepted,
    because "txt".contains("x").
    
    Consolidate into one implementation, HttpHelper.isFileNameExtWhitelisted in
    camel-http-base, reachable by both the http-common bindings and the vertx
    consumer:
    - DefaultHttpBinding.isFileNameAccepted now delegates to it (behaviour
      unchanged: exact, case-insensitive, comma-separated token match; * accepts
      everything; a name with no extension is accepted; the configured value is 
not
      modified).
    - VertxPlatformHttpConsumer calls it in place of the substring check. 
Uploads
      that only matched as a substring are now rejected on the vertx consumer 
too.
    
    The extension is still taken with FileUtil.onlyExt (not single mode), so a
    multi-dot name is matched on its full extension chain (archive.tar.gz needs 
a
    whitelist of tar.gz, not gz), which keeps a double-extension upload such as
    evil.php.jpg from passing a jpg whitelist.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../org/apache/camel/http/base/HttpHelper.java     | 35 +++++++++
 .../base/HttpHelperFileNameExtWhitelistTest.java   | 84 ++++++++++++++++++++++
 .../camel/http/common/DefaultHttpBinding.java      | 24 ++-----
 .../http/vertx/VertxPlatformHttpConsumer.java      | 16 ++---
 4 files changed, 127 insertions(+), 32 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 b12cb19ee633..da52065eb5f5 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;
 
@@ -261,4 +263,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 c46bb61fce3f..c54b9a6a22b8 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
@@ -56,7 +56,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;
@@ -364,25 +363,10 @@ public class DefaultHttpBinding implements HttpBinding {
      * @return          true if the file is accepted
      */
     protected 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;
-        }
-        for (String allowed : whitelist.split(",")) {
-            if (allowed.trim().equals(ext)) {
-                return true;
-            }
-        }
-        return false;
+        // 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
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 deedc4835303..883ce58ac09a 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
@@ -556,18 +556,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);

Reply via email to