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 2c904e48f132 CAMEL-24120: camel-smb - Fix double download and stream 
download file handle leak (#24788)
2c904e48f132 is described below

commit 2c904e48f13263407eaf366b2a420f7e84c44736
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 16 15:40:26 2026 +0200

    CAMEL-24120: camel-smb - Fix double download and stream download file 
handle leak (#24788)
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../org/apache/camel/component/smb/SmbFile.java    |  8 ++-
 .../component/smb/SmbFileClosingInputStream.java   | 46 +++++++++++++++++
 .../apache/camel/component/smb/SmbOperations.java  | 60 ++++++++++++++--------
 3 files changed, 90 insertions(+), 24 deletions(-)

diff --git 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFile.java
 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFile.java
index 106795247be9..417c4c6b1aa9 100644
--- 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFile.java
+++ 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFile.java
@@ -85,15 +85,19 @@ public class SmbFile extends 
GenericFile<FileIdBothDirectoryInformation> {
     }
 
     @Override
-    // NOTE: when the returned object is an InputStream, the client must close 
it.
     public Object getBody() {
         if (!download) {
             return null;
         }
+        // return the body already stored by the retrieve pipeline (via the 
binding)
+        Object body = super.getBody();
+        if (body != null) {
+            return body;
+        }
+        // lazy fetch for pollEnrich or other paths that skip retrieveFile
         if (streamDownload) {
             return operations.getBodyAsInputStream(exchange, 
this.getAbsoluteFilePath());
         } else {
-            // use operations so smb file can be closed
             return operations.getBody(this.getAbsoluteFilePath());
         }
     }
diff --git 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFileClosingInputStream.java
 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFileClosingInputStream.java
new file mode 100644
index 000000000000..7216f7bd26e1
--- /dev/null
+++ 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbFileClosingInputStream.java
@@ -0,0 +1,46 @@
+/*
+ * 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.smb;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.hierynomus.smbj.share.File;
+
+/**
+ * An InputStream wrapper that closes the underlying smbj {@link File} handle 
when the stream is closed. smbj's
+ * FileInputStream.close() does not close the File handle, so without this 
wrapper the remote SMB file handle leaks.
+ */
+class SmbFileClosingInputStream extends FilterInputStream {
+
+    private final File smbFile;
+
+    SmbFileClosingInputStream(InputStream delegate, File smbFile) {
+        super(delegate);
+        this.smbFile = smbFile;
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            super.close();
+        } finally {
+            smbFile.close();
+        }
+    }
+}
diff --git 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
index f3126e084762..3ef511cf7b83 100644
--- 
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
+++ 
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
@@ -310,27 +310,40 @@ public class SmbOperations implements SmbFileOperations {
 
         connectIfNecessary();
 
-        // read the entire file into memory in the byte array
-        try (File shareFile = share.openFile(name, 
EnumSet.of(AccessMask.GENERIC_READ), null,
-                SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OPEN, null)) {
-
-            if (configuration.isStreamDownload()) {
-                InputStream is = shareFile.getInputStream();
-                target.setBody(is);
-                exchange.getIn().setHeader(SmbConstants.SMB_FILE_INPUT_STREAM, 
is);
-            } else {
+        if (configuration.isStreamDownload()) {
+            // stream download: keep the File handle open so the InputStream 
remains valid;
+            // wrap in a SmbFileClosingInputStream that closes the File handle 
when the stream is closed
+            try {
+                File shareFile = share.openFile(name, 
EnumSet.of(AccessMask.GENERIC_READ), null,
+                        SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OPEN, 
null);
+                try {
+                    InputStream raw = shareFile.getInputStream();
+                    InputStream is = new SmbFileClosingInputStream(raw, 
shareFile);
+                    target.setBody(is);
+                    
exchange.getIn().setHeader(SmbConstants.SMB_FILE_INPUT_STREAM, is);
+                    exchange.getIn().setHeader(SmbConstants.SMB_UNC_PATH, 
shareFile.getUncPath());
+                } catch (Exception e) {
+                    IOHelper.close(shareFile);
+                    throw e;
+                }
+            } catch (SMBRuntimeException smbre) {
+                safeDisconnect(smbre);
+                throw smbre;
+            }
+        } else {
+            try (File shareFile = share.openFile(name, 
EnumSet.of(AccessMask.GENERIC_READ), null,
+                    SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OPEN, 
null)) {
                 try (InputStream is = shareFile.getInputStream()) {
                     byte[] body = is.readAllBytes();
                     target.setBody(body);
                 } catch (IOException e) {
                     throw new 
GenericFileOperationFailedException(e.getMessage(), e);
                 }
+                exchange.getIn().setHeader(SmbConstants.SMB_UNC_PATH, 
shareFile.getUncPath());
+            } catch (SMBRuntimeException smbre) {
+                safeDisconnect(smbre);
+                throw smbre;
             }
-
-            exchange.getIn().setHeader(SmbConstants.SMB_UNC_PATH, 
shareFile.getUncPath());
-        } catch (SMBRuntimeException smbre) {
-            safeDisconnect(smbre);
-            throw smbre;
         }
         return true;
     }
@@ -629,22 +642,25 @@ public class SmbOperations implements SmbFileOperations {
         }
     }
 
-    // The stream must be closed by the client.
     public InputStream getBodyAsInputStream(Exchange exchange, String path) {
         connectIfNecessary();
-        InputStream is;
         try {
-            // NOTE: the streams opened must be closed byt the client.
-            File shareFile = share.openFile(path, 
EnumSet.of(AccessMask.GENERIC_READ), null, // NOSONAR
+            File shareFile = share.openFile(path, 
EnumSet.of(AccessMask.GENERIC_READ), null,
                     SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OPEN, 
null);
-            is = shareFile.getInputStream();
-            exchange.getIn().setHeader(SmbComponent.SMB_FILE_INPUT_STREAM, is);
-            exchange.getIn().setHeader(SmbConstants.SMB_UNC_PATH, 
shareFile.getUncPath());
+            try {
+                InputStream raw = shareFile.getInputStream();
+                InputStream is = new SmbFileClosingInputStream(raw, shareFile);
+                exchange.getIn().setHeader(SmbComponent.SMB_FILE_INPUT_STREAM, 
is);
+                exchange.getIn().setHeader(SmbConstants.SMB_UNC_PATH, 
shareFile.getUncPath());
+                return is;
+            } catch (Exception e) {
+                IOHelper.close(shareFile);
+                throw e;
+            }
         } catch (SMBRuntimeException smbre) {
             safeDisconnect(smbre);
             throw smbre;
         }
-        return is;
     }
 
     private void safeDisconnect(SMBRuntimeException smbre) {

Reply via email to