You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Federico Mariani (Jira)" <ji...@apache.org> on 2026/07/15 14:02:00 UTC

[jira] [Created] (CAMEL-24088) camel-ftp: streamDownload treats a refused RETR as success - remote file is moved/deleted although it was never downloaded

Federico Mariani created CAMEL-24088:
----------------------------------------

             Summary: camel-ftp: streamDownload treats a refused RETR as success - remote file is moved/deleted although it was never downloaded
                 Key: CAMEL-24088
                 URL: https://issues.apache.org/jira/browse/CAMEL-24088
             Project: Camel
          Issue Type: Bug
          Components: camel-ftp
    Affects Versions: 4.21.0
            Reporter: Federico Mariani
         Attachments: FtpStreamDownloadRefusedRetrTest.java

h3. Problem
{{FTPClient.retrieveFileStream()}} returns *null* when the RETR command is refused (550 not-found/permission, 450 busy, 425 data-connection failure). {{FtpOperations.retrieveFileToStreamInBody}} (FtpOperations.java:483-487) ignores that contract:
{code:java}
if (endpoint.getConfiguration().isStreamDownload()) {
    InputStream is = client.retrieveFileStream(remoteName);
    target.setBody(is);
    exchange.getIn().setHeader(FtpConstants.REMOTE_FILE_INPUT_STREAM, is);
    result = true;                    // unconditional, even when is == null
}
{code}
{{GenericFileConsumer.tryRetrievingFile}} treats {{true}} as success, routes the exchange with a *null body*, and on completion the rename/delete process strategy commits - moving or deleting a remote file that was never transferred. The in-memory path checks {{result = client.retrieveFile(...)}} correctly.

h3. Failure scenario
{{ftp://...?streamDownload=true&move=done}} (or {{delete=true}}) and the server transiently refuses RETR under load (425/450): the exchange completes "successfully" with a null body and the remote file is moved/deleted - silent data loss. It also makes {{ignoreFileNotFoundOrPermissionError}} dead code in streamDownload mode: the 550-ignore logic in {{FtpConsumer.ignoreCannotRetrieveFile}} is only reached when retrieve returns false or throws, which the stream path never does.

h3. Related defect in the same area
On the release side, {{releaseRetrievedFileResources}} (FtpOperations.java:439-450) calls {{client.completePendingCommand()}} but *discards the boolean result*. Commons Net contract: a {{false}} return means the transfer did not complete (server closed the data connection early with clean EOF + 426/451 reply). The route then saw a truncated stream with no exception and the commit still moves/deletes the remote file. Both defects are the same root theme - the streamDownload path never checks the client's success indicators - and should be fixed together.

h3. History
{{result = true}} has been unconditional since streamDownload was introduced; no CAMEL fix ever touched the null case. {{FtpSimpleConsumeStreamingPartialReadIT}} covers route-side read failures (abrupt disconnect mid-read throws), not a refused RETR nor the clean-EOF-negative-reply case.

h3. Suggested fix
{{result = is != null;}} - the FTP reply headers already set after the operation then drive the existing 550-ignore logic. For the release side, check {{completePendingCommand()}} and raise so the commit does not run.

h3. Reproducer
Attached failing JUnit test (place in {{components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/}}). No FTP server needed - it stubs {{FTPClient.retrieveFileStream()}} to return null with reply code 550 and asserts {{retrieveFile()}} returns false.

----
_This issue was researched and filed by Claude Code on behalf of [~croway] (GitHub: Croway), as part of a deep code review of camel-file and camel-ftp. A failing JUnit reproducer is attached; it fails deterministically on current main (4.22.0-SNAPSHOT)._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)