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 5904e8ca2b96 CAMEL-25524: camel-mina-sftp - Write a file in the
charset of the endpoint and apply allowNullBody (#27675)
5904e8ca2b96 is described below
commit 5904e8ca2b963cbe1530d389bc6d1fcb7ccfbb71
Author: allthingssecurity <[email protected]>
AuthorDate: Sun Oct 11 12:39:02 2026 +0530
CAMEL-25524: camel-mina-sftp - Write a file in the charset of the endpoint
and apply allowNullBody (#27675)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../file/remote/mina/MinaSftpOperations.java | 23 +++++++-
.../mina/sftp/SftpProducerAllowNullBodyIT.java | 63 ++++++++++++++++++++++
.../mina/sftp/SftpProducerWithCharsetIT.java | 42 +++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 9 ++++
4 files changed, 136 insertions(+), 1 deletion(-)
diff --git
a/components/camel-mina-sftp/src/main/java/org/apache/camel/component/file/remote/mina/MinaSftpOperations.java
b/components/camel-mina-sftp/src/main/java/org/apache/camel/component/file/remote/mina/MinaSftpOperations.java
index 412b114be7ad..dac17391bf29 100644
---
a/components/camel-mina-sftp/src/main/java/org/apache/camel/component/file/remote/mina/MinaSftpOperations.java
+++
b/components/camel-mina-sftp/src/main/java/org/apache/camel/component/file/remote/mina/MinaSftpOperations.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.file.remote.mina;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -1343,8 +1344,28 @@ public class MinaSftpOperations implements
RemoteFileOperations<SftpRemoteFile>
}
}
+ InputStream is = null;
+ if (exchange.getIn().getBody() == null) {
+ // Do an explicit test for a null body and decide what to do
+ if (endpoint.isAllowNullBody()) {
+ LOG.trace("Writing empty file.");
+ is = new ByteArrayInputStream(new byte[0]);
+ } else {
+ throw new GenericFileOperationFailedException("Cannot write
null body to file: " + name);
+ }
+ }
+
try {
- InputStream is =
exchange.getIn().getMandatoryBody(InputStream.class);
+ if (is == null) {
+ String charset = endpoint.getCharset();
+ if (charset != null) {
+ // charset configured so we must convert to the desired
charset so we can write with encoding
+ is = GenericFileHelper.toInputStream(exchange, charset);
+ LOG.trace("Using InputStream {} with charset {}.", is,
charset);
+ } else {
+ is = exchange.getIn().getMandatoryBody(InputStream.class);
+ }
+ }
boolean append = endpoint.getFileExist() ==
GenericFileExist.Append;
diff --git
a/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerAllowNullBodyIT.java
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerAllowNullBodyIT.java
new file mode 100644
index 000000000000..ccb04ab859bc
--- /dev/null
+++
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerAllowNullBodyIT.java
@@ -0,0 +1,63 @@
+/*
+ * 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.component.file.remote.mina.sftp;
+
+import java.io.File;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.component.file.GenericFileOperationFailedException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIf;
+
+import static org.apache.camel.test.junit6.TestSupport.assertIsInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@EnabledIf(value =
"org.apache.camel.test.infra.ftp.services.embedded.SftpUtil#hasRequiredAlgorithms('src/test/resources/sftp/hostkey.pem')")
+class SftpProducerAllowNullBodyIT extends SftpServerTestSupport {
+
+ private String getSftpUri(String fileName) {
+ return
"mina-sftp://localhost:{{ftp.server.port}}/{{ftp.root.dir}}/allownull?username=admin&password=admin"
+ + "&fileName=" + fileName + "&knownHostsFile=" +
service.getKnownHostsFile();
+ }
+
+ @Test
+ void testAllowNullBodyTrue() {
+ template.sendBody(getSftpUri("allowNullBodyTrue.txt") +
"&allowNullBody=true", null);
+
+ File file = ftpFile("allownull/allowNullBodyTrue.txt").toFile();
+ assertTrue(file.exists(), "allowNullBody set to true with null body
should create an empty file");
+ assertEquals(0, file.length());
+ }
+
+ @Test
+ void testAllowNullBodyFalse() {
+ String uri = getSftpUri("allowNullBodyFalse.txt") +
"&allowNullBody=false";
+ Exception ex = assertThrows(CamelExecutionException.class, () ->
template.sendBody(uri, null));
+
+ GenericFileOperationFailedException cause
+ =
assertIsInstanceOf(GenericFileOperationFailedException.class, ex.getCause());
+ assertTrue(cause.getMessage().startsWith("Cannot write null body to
file:"),
+ "expected the explicit null body error, but was: " +
cause.getMessage());
+ assertTrue(cause.getMessage().endsWith("allowNullBodyFalse.txt"),
cause.getMessage());
+
+
assertFalse(ftpFile("allownull/allowNullBodyFalse.txt").toFile().exists(),
+ "allowNullBody set to false with null body should not create a
new file");
+ }
+}
diff --git
a/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerWithCharsetIT.java
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerWithCharsetIT.java
index 1aaea5c5c4f2..028b8ea502b0 100644
---
a/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerWithCharsetIT.java
+++
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/sftp/SftpProducerWithCharsetIT.java
@@ -17,12 +17,15 @@
package org.apache.camel.component.file.remote.mina.sftp;
import java.io.File;
+import java.nio.charset.StandardCharsets;
import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -51,6 +54,45 @@ public class SftpProducerWithCharsetIT extends
SftpServerTestSupport {
assertEquals(SAMPLE_FILE_PAYLOAD, storedPayload);
}
+ @Test
+ public void testProducerWithCharsetInRoute() throws Exception {
+ // the exchange comes from another endpoint, so it does not carry the
charset of the sftp endpoint
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").to(getSftpUri());
+ }
+ });
+
+ template.sendBodyAndHeader("direct:start", SAMPLE_FILE_PAYLOAD,
Exchange.FILE_NAME, SAMPLE_FILE_NAME);
+
+ File file = new File(service.getFtpRootDir() + "/" + SAMPLE_FILE_NAME);
+ assertTrue(file.exists(), "The uploaded file should exist");
+
+ String storedPayload = FileUtils.readFileToString(file,
SAMPLE_FILE_CHARSET);
+ assertEquals(SAMPLE_FILE_PAYLOAD, storedPayload, "The file must be
written in the charset of the endpoint");
+ }
+
+ @Test
+ public void testProducerWithCharsetReencodesBytesBody() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:bytes").to(getSftpUri());
+ }
+ });
+
+ // a body that is not a String is read in the charset of the exchange
(UTF-8 here) and written in the charset
+ // of the endpoint, as camel-sftp does: the stored bytes differ from
the bytes of the body
+ byte[] utf8 = SAMPLE_FILE_PAYLOAD.getBytes(StandardCharsets.UTF_8);
+ template.sendBodyAndHeader("direct:bytes", utf8, Exchange.FILE_NAME,
SAMPLE_FILE_NAME);
+
+ File file = new File(service.getFtpRootDir() + "/" + SAMPLE_FILE_NAME);
+ assertTrue(file.exists(), "The uploaded file should exist");
+
+ assertArrayEquals(SAMPLE_FILE_PAYLOAD.getBytes(SAMPLE_FILE_CHARSET),
FileUtils.readFileToByteArray(file));
+ }
+
private String getSftpUri() {
return
"mina-sftp://localhost:{{ftp.server.port}}/{{ftp.root.dir}}?username=admin&password=admin&charset="
+ SAMPLE_FILE_CHARSET + "&knownHostsFile=" +
service.getKnownHostsFile();
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 1023d0b9a751..ced580f92f30 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
@@ -2663,6 +2663,15 @@ resolves back inside the polled directory remains
accepted. Two configurations c
server that reports names navigating above the polled directory, and a
`fileName` expression (used when
`useList=false`) that navigates above it. Set `jailStartingDirectory=false` if
such a path is intended.
+=== camel-mina-sftp - the producer applies the charset and allowNullBody
options
+
+The producer wrote a String body in the charset of the exchange (UTF-8 unless
the exchange sets another one) and
+ignored the `charset` option of the endpoint, unless the exchange had been
created by that endpoint (for example by a
+`ProducerTemplate` sending to it directly). It now writes the file in the
configured `charset`, as camel-sftp does. A
+null body with `allowNullBody=true` now writes an empty file instead of
failing. When `charset` is set, non-String
+bodies (such as `byte[]`, `InputStream` or a file) are also read in the
charset of the exchange and re-encoded to that
+charset, where their bytes were written as they are before; do not set
`charset` for binary payloads.
+
=== camel-salesforce
The `camel-salesforce-maven-plugin` now supports JWT and Client Credentials
authentication in addition to