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