From 3a743802c0d79e2bd671ff1a71552b83b01c7790 Mon Sep 17 00:00:00 2001 From: abilan Date: Fri, 16 Dec 2022 12:16:58 -0500 Subject: [PATCH] GH-3974: Fix `SftpSession` for absolute paths Fixes https://github.com/spring-projects/spring-integration/issues/3974 * Treat a leading `/` as an indicator of absolute path request in the `SftpSession.doList()` * Call `sftpClient.canonicalPath()` for path without a leading `/` to resolve it as relative path in the user home * Add a note in docs for `LS` command --- .../integration/sftp/session/SftpSession.java | 7 +++++-- .../SftpInboundRemoteFileSystemSynchronizerTests.java | 3 ++- .../integration/sftp/inbound/SftpMessageSourceTests.java | 6 +++--- .../integration/sftp/outbound/SftpOutboundTests.java | 6 +++--- src/reference/asciidoc/sftp.adoc | 4 +++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java index b888681826..bb73f991ba 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java @@ -78,7 +78,7 @@ public class SftpSession implements Session { } public Stream doList(String path) throws IOException { - String remotePath = StringUtils.trimTrailingCharacter(StringUtils.trimLeadingCharacter(path, '/'), '/'); + String remotePath = StringUtils.trimTrailingCharacter(path, '/'); String remoteDir = remotePath; int lastIndex = remotePath.lastIndexOf('/'); if (lastIndex > 0) { @@ -96,7 +96,10 @@ public class SftpSession implements Session { remoteDir = remotePath; } } - remoteDir = remoteDir.length() == 0 ? this.sftpClient.canonicalPath("") : remoteDir; + remoteDir = + remoteDir.length() > 0 && remoteDir.charAt(0) == '/' + ? remoteDir + : this.sftpClient.canonicalPath(remoteDir); return StreamSupport.stream(this.sftpClient.readDir(remoteDir).spliterator(), false) .filter((entry) -> !isPattern || PatternMatchUtils.simpleMatch(remoteFile, entry.getFilename())); } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java index 1c99b85f2a..f425c91191 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java @@ -189,13 +189,14 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { try { SftpClient sftpClient = mock(SftpClient.class); + when(sftpClient.canonicalPath("remote-test-dir")).thenReturn("/remote-test-dir"); String[] files = new File("remote-test-dir").list(); for (String fileName : files) { when(sftpClient.read("remote-test-dir/" + fileName)) .thenReturn(new FileInputStream("remote-test-dir/" + fileName)); } - when(sftpClient.readDir("remote-test-dir")).thenReturn(this.sftpEntries); + when(sftpClient.readDir("/remote-test-dir")).thenReturn(this.sftpEntries); return SftpTestSessionFactory.createSftpSession(sftpClient); } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpMessageSourceTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpMessageSourceTests.java index 16ed45ee7e..399738e9cd 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpMessageSourceTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpMessageSourceTests.java @@ -44,7 +44,7 @@ public class SftpMessageSourceTests extends SftpTestSupport { private ApplicationContext context; @Test - public void testMaxFetch() throws Exception { + public void testMaxFetch() { SftpInboundFileSynchronizingMessageSource messageSource = buildSource(); Message received = messageSource.receive(); assertThat(received).isNotNull(); @@ -52,9 +52,9 @@ public class SftpMessageSourceTests extends SftpTestSupport { .isIn(" sftpSource1.txt", "sftpSource2.txt"); } - private SftpInboundFileSynchronizingMessageSource buildSource() throws Exception { + private SftpInboundFileSynchronizingMessageSource buildSource() { SftpInboundFileSynchronizer sync = new SftpInboundFileSynchronizer(sessionFactory()); - sync.setRemoteDirectory("sftpSource/"); + sync.setRemoteDirectory("/sftpSource/"); sync.setBeanFactory(this.context); SftpInboundFileSynchronizingMessageSource messageSource = new SftpInboundFileSynchronizingMessageSource(sync); messageSource.setLocalDirectory(getTargetLocalDirectory()); diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java index 3aeef4d07e..169b2d49b7 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java @@ -174,14 +174,14 @@ public class SftpOutboundTests { context.close(); } - @Test //INT-2275 + @Test public void testFtpOutboundGatewayInsideChain() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "SftpOutboundInsideChainTests-context.xml", getClass()); MessageChannel channel = context.getBean("outboundGatewayInsideChain", MessageChannel.class); - channel.send(MessageBuilder.withPayload("remote-test-dir").build()); + channel.send(MessageBuilder.withPayload("/remote-test-dir").build()); PollableChannel output = context.getBean("replyChannel", PollableChannel.class); @@ -308,7 +308,7 @@ public class SftpOutboundTests { Arrays.stream(files) .map((file) -> new SftpClient.DirEntry(file, file, new SftpClient.Attributes())) .toList(); - when(sftpClient.readDir("remote-test-dir")).thenReturn(dirEntries); + when(sftpClient.readDir("/remote-test-dir")).thenReturn(dirEntries); return SftpTestSessionFactory.createSftpSession(sftpClient); } diff --git a/src/reference/asciidoc/sftp.adoc b/src/reference/asciidoc/sftp.adoc index 09d6a33dd1..e7b05eb7bc 100644 --- a/src/reference/asciidoc/sftp.adoc +++ b/src/reference/asciidoc/sftp.adoc @@ -945,6 +945,8 @@ When using the recursive option (`-R`), the `fileName` includes any subdirectory If you use the `-dirs` option, each recursive directory is also returned as an element in the list. In this case, we recommend that you not use the `-1` option, because you would not be able to distinguish files from directories, which you can do when you use `FileInfo` objects. +If remote path to list starts with a `/` symbol, it is treated by SFTP as an absolute path; without - as a relative path in the current user home. + ==== Using `nlst` Command Version 5 introduced support for the `nlst` command. @@ -1341,7 +1343,7 @@ Currently, supported events are: * `PathRemovedEvent` - a file or directory was removed * `SessionClosedEvent` - the client has disconnected -Each of these is a subclass of `ApacheMinaSftpEvent`; you can configure a single listener to receive all of the event types. +Each of these is a subclass of `ApacheMinaSftpEvent`; you can configure a single listener to receive all the event types. The `source` property of each event is a `ServerSession`, from which you can obtain information such as the client address; a convenient `getSession()` method is provided on the abstract event. To configure the server with the listener (which must be a Spring bean), simply add it to the `SftpSubsystemFactory`: