This is an automated email from the ASF dual-hosted git repository.

twolf pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git


The following commit(s) were added to refs/heads/master by this push:
     new 794122f0b fix: negate followLinks flag for checkSymlinkState (#843)
794122f0b is described below

commit 794122f0bfb3df30cac8a7d61b2337d97a9986c0
Author: Joshua Winkler <[email protected]>
AuthorDate: Sat Oct 18 16:12:14 2025 +0200

    fix: negate followLinks flag for checkSymlinkState (#843)
    
    Missing boolean negations in two places led to wrong behavior.
    
    Add tests for the two cases: fstat and file deletion with symlinks in the 
path.
---
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  2 +-
 .../org/apache/sshd/sftp/server/SftpSubsystem.java |  2 +-
 .../apache/sshd/sftp/client/SftpVersionsTest.java  | 87 ++++++++++++++++++++++
 3 files changed, 89 insertions(+), 2 deletions(-)

diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
index 1c974f538..a2e66fcf3 100755
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/AbstractSftpSubsystemHelper.java
@@ -1717,7 +1717,7 @@ public abstract class AbstractSftpSubsystemHelper
         LinkOption[] options = accessor.resolveFileAccessLinkOptions(
                 this, resolvedPath, SftpConstants.SSH_FXP_REMOVE, "", false);
         WithFileAttributeCache.withAttributeCache(resolvedPath, p -> {
-            Boolean status = checkSymlinkState(p, followLinks, options);
+            Boolean status = checkSymlinkState(p, !followLinks, options);
             if (status == null) {
                 throw signalRemovalPreConditionFailure(id, path, p,
                         new AccessDeniedException(p.toString(), p.toString(), 
"Cannot determine existence of remove candidate"),
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
index 956b5aba3..f619c7783 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/SftpSubsystem.java
@@ -845,7 +845,7 @@ public class SftpSubsystem
                 this, file, SftpConstants.SSH_FXP_FSTAT, "", true);
 
         boolean followLinks = 
resolvePathResolutionFollowLinks(SftpConstants.SSH_FXP_FSTAT, handle, file);
-        return resolveFileAttributes(file, flags, followLinks, options);
+        return resolveFileAttributes(file, flags, !followLinks, options);
     }
 
     @Override
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
index 1a8ad9c98..f43f6b502 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpVersionsTest.java
@@ -49,6 +49,7 @@ import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils;
 import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder;
+import org.apache.sshd.common.util.OsUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.channel.ChannelSession;
 import org.apache.sshd.server.command.Command;
@@ -70,6 +71,7 @@ import org.apache.sshd.sftp.server.SftpSubsystem;
 import org.apache.sshd.sftp.server.SftpSubsystemEnvironment;
 import org.apache.sshd.sftp.server.SftpSubsystemFactory;
 import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.MethodOrderer.MethodName;
 import org.junit.jupiter.api.TestMethodOrder;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -569,6 +571,91 @@ public class SftpVersionsTest extends 
AbstractSftpClientTestSupport {
         }
     }
 
+    @MethodSource("parameters")
+    @ParameterizedTest(name = "version={0}")
+    public void sftpRemoveFileWithSymlinkInPath(int version) throws Exception {
+        Assumptions.assumeFalse(OsUtils.isWin32(), "Symlinks not reliably 
supported on Windows");
+
+        initSftpVersionsTest(version);
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(
+                targetPath,
+                SftpConstants.SFTP_SUBSYSTEM_NAME,
+                getClass().getSimpleName());
+        Path lclParent = assertHierarchyTargetFolderExists(lclSftp);
+        String testFileName = "test-file.txt";
+
+        Path actualDir = lclParent.resolve("actual-dir-" + getTestedVersion());
+        CommonTestSupportUtils.deleteRecursive(actualDir);
+        Files.createDirectories(actualDir);
+
+        Path symlinkDir = lclParent.resolve("symlink-dir-" + 
getTestedVersion());
+        Files.deleteIfExists(symlinkDir);
+        Files.createSymbolicLink(symlinkDir, actualDir);
+
+        Path fileViaSymlink = symlinkDir.resolve(testFileName);
+        Files.write(fileViaSymlink, "test 
content".getBytes(StandardCharsets.UTF_8));
+
+        assertTrue(Files.exists(fileViaSymlink), "File should exist via 
symlink path");
+        assertTrue(Files.exists(actualDir.resolve(testFileName)), "File should 
exist in actual directory");
+
+        Path parentPath = targetPath.getParent();
+        String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, fileViaSymlink);
+
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            sftp.remove(remotePath);
+
+            assertFalse(Files.exists(fileViaSymlink), "File should be deleted 
via symlink path");
+            assertFalse(Files.exists(actualDir.resolve(testFileName)), "File 
should be deleted from actual directory");
+        } finally {
+            Files.deleteIfExists(symlinkDir);
+            CommonTestSupportUtils.deleteRecursive(actualDir);
+        }
+    }
+
+    @MethodSource("parameters")
+    @ParameterizedTest(name = "version={0}")
+    public void sftpFStatWithSymlinkInPath(int version) throws Exception {
+        Assumptions.assumeFalse(OsUtils.isWin32(), "Symlinks not reliably 
supported on Windows");
+
+        initSftpVersionsTest(version);
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+                SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+        Path lclParent = assertHierarchyTargetFolderExists(lclSftp);
+
+        Path actualDir = lclParent.resolve("actual-fstat-" + 
getTestedVersion());
+        CommonTestSupportUtils.deleteRecursive(actualDir);
+        Files.createDirectories(actualDir);
+
+        Path symlinkDir = lclParent.resolve("symlink-fstat-" + 
getTestedVersion());
+        Files.deleteIfExists(symlinkDir);
+        Files.createSymbolicLink(symlinkDir, actualDir);
+
+        Path testFile = symlinkDir.resolve("test.txt");
+        String content = getCurrentTestName();
+        Files.write(testFile, content.getBytes(StandardCharsets.UTF_8));
+
+        Path parentPath = targetPath.getParent();
+        String remotePath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile);
+
+        try (ClientSession session = createAuthenticatedClientSession();
+             SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+            try (SftpClient.CloseableHandle handle = sftp.open(remotePath, 
SftpClient.OpenMode.Read)) {
+                Attributes attrs = sftp.stat(handle);
+
+                assertNotNull(attrs, "Attributes should be retrieved");
+                assertEquals(content.length(), attrs.getSize(), "File size 
mismatch");
+                assertTrue(attrs.isRegularFile(), "Should be a regular file");
+            }
+        } finally {
+            Files.deleteIfExists(testFile);
+            Files.deleteIfExists(symlinkDir);
+            CommonTestSupportUtils.deleteRecursive(actualDir);
+        }
+    }
+
     @Override
     public String toString() {
         return getClass().getSimpleName() + "[" + getTestedVersion() + "]";

Reply via email to