Skip to content

GH-11121: Fix SftpSession.listNames() for root directory wildcard paths#11122

Open
yangadam wants to merge 1 commit into
spring-projects:mainfrom
yangadam:GH-11121
Open

GH-11121: Fix SftpSession.listNames() for root directory wildcard paths#11122
yangadam wants to merge 1 commit into
spring-projects:mainfrom
yangadam:GH-11121

Conversation

@yangadam

@yangadam yangadam commented Jun 16, 2026

Copy link
Copy Markdown

Summary

SftpSession.listNames("/*") throws SftpException: SSH_FX_NO_SUCH_FILE because the path parsing in SftpSession.list() uses lastIndex > 0 to detect the / separator. For root-level paths like /*, lastIndexOf('/') returns 0, which is excluded by the > 0 check. This causes the entire path /* to be treated as a literal directory name instead of being split into directory / and file pattern *.

Changes

  • Changed lastIndex > 0 to lastIndex >= 0 in SftpSession.list() so root directory paths are correctly parsed
  • Added test lsRootWildcard() that reproduces the issue and verifies the fix

Fixes: gh-11121

Signed-off-by: Adam Yang yangadam@users.noreply.github.com

@yangadam yangadam changed the base branch from main to 6.0.x June 16, 2026 13:21
@yangadam yangadam closed this Jun 16, 2026
@yangadam yangadam deleted the GH-11121 branch June 16, 2026 13:46
@yangadam yangadam restored the GH-11121 branch June 16, 2026 13:52
@yangadam yangadam reopened this Jun 16, 2026
@yangadam yangadam changed the base branch from 6.0.x to main June 16, 2026 13:54

@artembilan artembilan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, looks like you have not checked the fix locally before opening PR:

SftpOutboundTests > testFtpOutboundGatewayInsideChain() FAILED
    org.springframework.messaging.MessageHandlingException at SftpOutboundTests.java:192
        Caused by: org.springframework.messaging.MessagingException at SftpOutboundTests.java:192
            Caused by: java.lang.NullPointerException at SftpOutboundTests.java:192

Please, ensure you run ./gradlew "spring-integration-sftp:check locally before pushing to PR.

Thanks

@yangadam

Copy link
Copy Markdown
Author

Well, looks like you have not checked the fix locally before opening PR:

SftpOutboundTests > testFtpOutboundGatewayInsideChain() FAILED
    org.springframework.messaging.MessageHandlingException at SftpOutboundTests.java:192
        Caused by: org.springframework.messaging.MessagingException at SftpOutboundTests.java:192
            Caused by: java.lang.NullPointerException at SftpOutboundTests.java:192

Please, ensure you run ./gradlew "spring-integration-sftp:check locally before pushing to PR.

Thanks

Thanks for running the build. After debugging, I found that the failure was caused by the lack of mocking for the stat method, which previously was not called when the path was /remote-test-dir/.

I can add the mocking, but I need to confirm whether it is acceptable to call stat whenever the path is /remote-test-dir/ or /remote-test-dir/remote-sub-dir/.

@artembilan

…ry wildcard paths

Change `lastIndex > 0` to `lastIndex >= 0` in `SftpSession.doList()` so
that root-level paths like `/*` are correctly split into directory and
file components. Previously, `lastIndexOf('/')` returning `0` was not
handled, causing the entire path to be treated as a directory name.

Fixes: spring-projectsgh-11121

Signed-off-by: Adam Yang <yangmm.adam@gmail.com>

@artembilan artembilan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to confirm whether it is acceptable to call stat whenever the path is /remote-test-dir/ or /remote-test-dir/remote-sub-dir/.

Correct. So, ask yourself if it is valid to divide /remote-test-dir into / and remote-test-dir as you do in your current fix.
You covered with boolean isPattern = remoteFile != null && remoteFile.contains("*");.
But that is not OK for a dir request.

I think we need just trim both leading and trailing / in the method you have modified instead.
Something like:

String remotePath = StringUtils.trimLeadingCharacter(StringUtils.trimTrailingCharacter(validPath, '/'), '/');

With that your /* will become * as you expect.

@yangadam

Copy link
Copy Markdown
Author

Hi @artembilan, thanks for taking the time to review and for the suggestion!

I looked into the trimming approach, but I think the split-then-stat pattern might actually be unavoidable here. A name like remote-test-dir could be either a file or a directory — we can never know beforehand without calling stat.

In fact, the existing code on main already does this for deeper paths. For example, /remote-test-dir/remote-sub-dir/ goes through the same flow:

  1. trimTrailingCharacter/remote-test-dir/remote-sub-dir
  2. lastIndex = 21 (> 0), so remoteDir = "/remote-test-dir", remoteFile = "remote-sub-dir"
  3. isPattern = false → enters the stat block → discovers it's a directory → reassigns remoteDir = remotePath

My fix for lastIndex == 0 simply makes /remote-test-dir follow the same existing pattern — the only difference is the nesting depth. In both cases, the last path component could be a file or a directory, and stat is how the code figures that out.

Would it be OK to keep this approach consistent with the existing behavior for now, and refactor the method later if we'd like to optimize it?

@artembilan artembilan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add your name to the @author list of all the affected classes.

Then we'll merge.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SftpSession.listNames() throws SSH_FX_NO_SUCH_FILE for root directory wildcard paths

2 participants