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

davsclaus 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 0099984f8365 CAMEL-24650: camel-http-common - Lift the multipart 
fileNameExtWhitelist check into DefaultHttpBinding (#26872)
0099984f8365 is described below

commit 0099984f836579b0ca53f95ff81f7870cd7e24eb
Author: Urmila Unni <[email protected]>
AuthorDate: Mon Sep 28 17:58:28 2026 +0530

    CAMEL-24650: camel-http-common - Lift the multipart fileNameExtWhitelist 
check into DefaultHttpBinding (#26872)
    
    Share one isFileNameAccepted method on DefaultHttpBinding between the
    servlet and jetty attachment bindings, keeping the CAMEL-24427 behaviour:
    exact, case-insensitive matching per extension, * accepts everything, and
    files without an extension are accepted.
    
    DefaultHttpBinding's own older copy of the check (the request-attribute
    path in populateAttachments) now uses it too. It matched extensions as a
    substring and overwrote the configured fileNameExtWhitelist, so this fixes
    a whitelist bypass there. Adds a 4.23 upgrade guide note.
    
    Co-authored-by: Claude <[email protected]>
---
 .../camel/http/common/DefaultHttpBinding.java      | 45 +++++++++++----
 .../camel/http/common/DefaultHttpBindingTest.java  | 67 ++++++++++++++++++++++
 .../component/jetty12/AttachmentHttpBinding.java   | 26 ---------
 .../component/servlet/AttachmentHttpBinding.java   | 23 +-------
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  8 +++
 5 files changed, 110 insertions(+), 59 deletions(-)

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 f334e6e0c8e9..c46bb61fce3f 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
@@ -339,17 +339,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)));
@@ -362,6 +352,39 @@ 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) {
+        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;
+    }
+
     @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 80c7ea14f6ac..8809c9b8ed8c 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
@@ -25,7 +25,9 @@ import org.apache.camel.test.junit6.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 {
 
@@ -70,4 +72,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-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;
 
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 8bcae8034fb8..748d32d26156 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2770,6 +2770,14 @@ two names differed, that header never carried a usable 
`DataHandler` in the firs
 names happened to be equal did it resolve, and then the name is unchanged. A 
route that expected the
 attachment header under the uploaded file name should read it under the 
multipart field name.
 
+Both bindings now share this check through `DefaultHttpBinding` in 
`camel-http-common`. `DefaultHttpBinding`
+also carried an older copy of it for uploads that arrive as request 
attributes, which matched each
+extension as a substring of the whitelist, so for example a whitelist of `txt` 
accepted an upload named
+`evil.x`, and it replaced the configured `fileNameExtWhitelist` with its 
lower-cased value. It now uses
+the shared check: each comma-separated entry is compared exactly and 
case-insensitively, `*` still
+accepts every file, and the configured value is left unchanged. An upload 
whose extension only matched
+as part of a longer entry is now rejected.
+
 === camel-tensorflow-serving - the Target and Credentials headers are 
deprecated
 
 `TensorFlowServingConstants.TARGET` (`CamelTensorFlowServingTarget`) and

Reply via email to