You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/10/11 07:09:10 UTC

(camel) branch main updated: CAMEL-25524: camel-mina-sftp - Write a file in the charset of the endpoint and apply allowNullBody (#27675)

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 <49...@users.noreply.github.com>
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 <no...@anthropic.com>
---
 .../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