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

Reply via email to