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);