This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24120 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7cd9a5feca6332f0a07c2916d5a3b22cad960453 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jul 16 13:28:57 2026 +0200 CAMEL-24120: camel-smb - Fix double download and stream download file handle leak SmbFile.getBody() bypassed the body stored by retrieveFile and always re-downloaded the file from the server. Fixed to return the binding body first, falling back to lazy fetch only when no body was stored. For streamDownload=true, the smbj File handle was leaked because FileInputStream.close() does not close the underlying File. Introduced SmbFileClosingInputStream wrapper whose close() also closes the smbj File handle. Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[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 b0c302133952..081a350c6c90 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 @@ -304,27 +304,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; } @@ -622,22 +635,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) {
