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 ba49fed97 GH-751: Fix "long name" for SFTP v3
ba49fed97 is described below

commit ba49fed975846a0d770eb855be0c1d5d26583864
Author: Thomas Wolf <tw...@apache.org>
AuthorDate: Sat May 10 18:02:34 2025 +0200

    GH-751: Fix "long name" for SFTP v3
    
    The "long name" in a SFTP v3 SSH_FXP_NAME record as returned when
    listing a directory is supposed to contain some representation suitable
    for showing to the user directly; the draft RFC recommends a format
    similar to Linux "ls -al", and Apache MINA sshd implements that strictly
    as proposed in the draft RFC.
    
    However, there were a few problems.
    
    First, while the "long name" would contain a size also for directory
    entries, the corresponding SFTP attributes always had size zero. Fix
    this by reporting the size in the attributes not only for files but
    simply for all entries, including directories.
    
    Second, when an SFTP server itself was based on an SftpFileSystem
    (chaining of SFTP servers, which is not ideal, but is possible), the
    owner info was lost because the server uses a standard DirectoryStream.
    Fix this by propagating the upstream "long name" through the cached
    attributes, and using it, if available, when generating the server's
    own "long name" to send to the client. (So the server passes through
    the "long name" from the upstream server, if there is one.)
    
    To fix this also for the "." and ".." entries, which a DirectoryStream
    normally does not report but which we _do_ get from the upstream SFTP
    server, add a mechanism to create a not-quite-standard DirectoryStream
    that _does_ include these entries. That way, the server has access to
    the attributes cached from the upstream server, and can report correct
    ownership info also for these entries.
---
 CHANGES.md                                         |   1 +
 .../org/apache/sshd/sftp/client/SftpClient.java    |  11 +-
 .../sshd/sftp/client/fs/SftpPathIterator.java      |  38 ++++---
 .../apache/sshd/sftp/client/fs/impl/SftpUtils.java |  47 ++++++++
 .../org/apache/sshd/sftp/common/SftpHelper.java    |  27 +++--
 .../sftp/server/AbstractSftpSubsystemHelper.java   |  71 +++++++-----
 .../apache/sshd/sftp/server/DirectoryHandle.java   |  24 ++++-
 .../sshd/sftp/client/fs/SftpFileSystemTest.java    | 119 +++++++++++++++++++++
 8 files changed, 282 insertions(+), 56 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index bb1c8133f..7533a2a77 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -38,6 +38,7 @@
 * [GH-709](https://github.com/apache/mina-sshd/issues/709) `AbstractChannel`: 
Handle keep-alive channel messages sent by an old OpenSSH server
 * [GH-727](https://github.com/apache/mina-sshd/issues/727) Supply default port 
22 for proxy jump hosts for which there is no `HostConfigEntry`
 * [GH-733](https://github.com/apache/mina-sshd/issues/733) Fix 
`SftpRemotePathChannel.transferTo()` (avoid NPE)
+* [GH-751](https://github.com/apache/mina-sshd/issues/751) Fix SFTP v3 "long 
name" if SFTP server uses an `SftpFileSystem` to another server
 
 
 * [SSHD-1343](https://issues.apache.org/jira/projects/SSHD/issues/SSHD-1343) 
Correct documentation in `ChannelDataReceiver`
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
index 0adf2e41b..d41a4275f 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java
@@ -222,6 +222,7 @@ public interface SftpClient extends SubsystemClient {
         private FileTime modifyTime;
         private List<AclEntry> acl;
         private Map<String, byte[]> extensions = Collections.emptyMap();
+        private String longName;
 
         public Attributes() {
             super();
@@ -456,12 +457,20 @@ public interface SftpClient extends SubsystemClient {
             return !isRegularFile() && !isDirectory() && !isSymbolicLink();
         }
 
+        public String longName() {
+            return longName;
+        }
+
+        public void setLongName(String longName) {
+            this.longName = longName;
+        }
+
         @Override
         public String toString() {
             return "type=" + getType() + ";size=" + getSize() + ";uid=" + 
getUserId() + ";gid=" + getGroupId() + ";perms=0x"
                    + Integer.toHexString(getPermissions()) + ";flags=" + 
getFlags() + ";owner=" + getOwner() + ";group="
                    + getGroup() + ";aTime=" + getAccessTime() + ";cTime=" + 
getCreateTime() + ";mTime=" + getModifyTime()
-                   + ";extensions=" + getExtensions().keySet();
+                   + ";longName=" + longName + ";extensions=" + 
getExtensions().keySet();
         }
     }
 
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java
index 76c89fc6e..af18d6315 100644
--- 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java
+++ 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/SftpPathIterator.java
@@ -30,6 +30,7 @@ import java.util.NoSuchElementException;
 import java.util.Objects;
 
 import org.apache.sshd.sftp.client.SftpClient;
+import org.apache.sshd.sftp.client.fs.impl.SftpUtils;
 
 /**
  * Implements and {@link Iterator} of {@link SftpPath}-s returned by a {@link 
DirectoryStream#iterator()} method.
@@ -44,6 +45,7 @@ public class SftpPathIterator implements Iterator<Path> {
 
     private final SftpPath path;
     private DirectoryStream.Filter<? super Path> filter;
+    private boolean withDots;
 
     public SftpPathIterator(SftpPath path, Iterable<? extends 
SftpClient.DirEntry> iter) {
         this(path, iter, null);
@@ -62,7 +64,10 @@ public class SftpPathIterator implements Iterator<Path> {
                             DirectoryStream.Filter<? super Path> filter) {
         this.path = Objects.requireNonNull(path, "No root path provided");
         this.filter = filter;
-
+        this.withDots = 
Boolean.TRUE.equals(SftpUtils.DIRECTORY_WITH_DOTS.get());
+        if (withDots) {
+            SftpUtils.DIRECTORY_WITH_DOTS.set(null);
+        }
         it = iter;
         curEntry = nextEntry(path, filter);
     }
@@ -109,22 +114,25 @@ public class SftpPathIterator implements Iterator<Path> {
         while ((it != null) && it.hasNext()) {
             SftpClient.DirEntry entry = it.next();
             String name = entry.getFilename();
-            if (".".equals(name) && (!dotIgnored)) {
-                dotIgnored = true;
-            } else if ("..".equals(name) && (!dotdotIgnored)) {
-                dotdotIgnored = true;
-            } else {
-                SftpPath candidate = root.resolve(entry.getFilename());
-                if (candidate instanceof WithFileAttributeCache) {
-                    ((WithFileAttributeCache) 
candidate).setAttributes(entry.getAttributes());
+            if (!withDots) {
+                if (".".equals(name) && !dotIgnored) {
+                    dotIgnored = true;
+                    continue;
+                } else if ("..".equals(name) && !dotdotIgnored) {
+                    dotdotIgnored = true;
+                    continue;
                 }
-                try {
-                    if ((selector == null) || selector.accept(candidate)) {
-                        return candidate;
-                    }
-                } catch (IOException e) {
-                    throw new DirectoryIteratorException(e);
+            }
+            SftpPath candidate = root.resolve(name);
+            if (candidate instanceof WithFileAttributeCache) {
+                ((WithFileAttributeCache) 
candidate).setAttributes(entry.getAttributes());
+            }
+            try {
+                if ((selector == null) || selector.accept(candidate)) {
+                    return candidate;
                 }
+            } catch (IOException e) {
+                throw new DirectoryIteratorException(e);
             }
         }
 
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/impl/SftpUtils.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/impl/SftpUtils.java
new file mode 100644
index 000000000..d64d99687
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/fs/impl/SftpUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.sshd.sftp.client.fs.impl;
+
+/**
+ * Internal utilities for SFTP file systems.
+ *
+ * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
+ */
+public final class SftpUtils {
+
+    /**
+     * For internal uses, this can be set to Boolean.TRUE to indicate that a 
SftpDirectoryStream created shall report
+     * the "." and ".." entries (which Java normally doesn't do, but which we 
do get from the SFTP server). The stream,
+     * if it recognizes this, will set the value to {@code null}, so code 
using this can check after having created the
+     * stream whether it will return these entries (value is {@code null}). 
Intended usage:
+     *
+     * <pre>
+     * SftpUtils.DIRECTORY_WITH_DOTS.set(Boolean.TRUE);
+     * DirectoryStream&lt;Path&gt; dir = Files.newDirectoryStream(somePath);
+     * boolean withDots = SftpUtils.DIRECTORY_WITH_DOTS.get() == null;
+     * SftpUtil.DIRECTORY_WITH_DOTS.remove();
+     * </pre>
+     */
+    public static final ThreadLocal<Boolean> DIRECTORY_WITH_DOTS = new 
ThreadLocal<>();
+
+    private SftpUtils() {
+        throw new IllegalStateException("No instantiation");
+    }
+
+}
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
index c39e61c44..b6e501331 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/common/SftpHelper.java
@@ -239,7 +239,7 @@ public final class SftpHelper {
         FileTime lastModifiedTime = (FileTime) 
attributes.get(IoUtils.LASTMOD_TIME_VIEW_ATTR);
         FileTime lastAccessTime = (FileTime) 
attributes.get(IoUtils.LASTACC_TIME_VIEW_ATTR);
         Map<?, ?> extensions = (Map<?, ?>) 
attributes.get(IoUtils.EXTENDED_VIEW_ATTR);
-        int flags = ((isReg || isLnk) && (size != null) ? 
SftpConstants.SSH_FILEXFER_ATTR_SIZE : 0)
+        int flags = (size != null ? SftpConstants.SSH_FILEXFER_ATTR_SIZE : 0)
                     | (attributes.containsKey(IoUtils.USERID_VIEW_ATTR) && 
attributes.containsKey(IoUtils.GROUPID_VIEW_ATTR)
                             ? SftpConstants.SSH_FILEXFER_ATTR_UIDGID : 0)
                     | ((perms != null) ? 
SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS : 0)
@@ -627,20 +627,19 @@ public final class SftpHelper {
      * @return          {@code attrs}
      */
     public static Attributes complete(Attributes attrs, String longName) {
-        if (longName == null || longName.isEmpty()) {
-            return attrs;
-        }
-        if (attrs.getType() == SftpConstants.SSH_FILEXFER_TYPE_UNKNOWN //
-                && (attrs.getPermissions() & SftpConstants.S_IFMT) == 0 //
-                && isUnixPermissions(longName)) {
-            // Some SFTP v3 servers do not send the file type flags in the 
permissions. The draft RFC does not
-            // explicitly say they should be included... if we have a 
longname, it's SFTP v3, and it should start
-            // with the permissions string as in POSIX/Linux "ls -l". The 
first character determines the file type.
-            int type = fileTypeFromChar(longName.charAt(0));
-            if (type != SftpConstants.SSH_FILEXFER_TYPE_UNKNOWN) {
-                attrs.setType(type);
-                attrs.setPermissions(attrs.getPermissions() | 
fileTypeToPermission(type));
+        if (!GenericUtils.isEmpty(longName)) {
+            if (attrs.getType() == SftpConstants.SSH_FILEXFER_TYPE_UNKNOWN //
+                    && (attrs.getPermissions() & SftpConstants.S_IFMT) == 0 //
+                    && isUnixPermissions(longName)) {
+                // Some SFTP v3 servers do not send the file type flags in the 
permissions. The draft RFC does not
+                // explicitly say they should be included... The first 
character determines the file type.
+                int type = fileTypeFromChar(longName.charAt(0));
+                if (type != SftpConstants.SSH_FILEXFER_TYPE_UNKNOWN) {
+                    attrs.setType(type);
+                    attrs.setPermissions(attrs.getPermissions() | 
fileTypeToPermission(type));
+                }
             }
+            attrs.setLongName(longName);
         }
         return attrs;
     }
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 f9bd266ec..1c974f538 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
@@ -65,6 +65,7 @@ import java.util.TreeSet;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.IntUnaryOperator;
+import java.util.function.Predicate;
 
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedFactory;
@@ -2239,33 +2240,52 @@ public abstract class AbstractSftpSubsystemHelper
                 this, dir.getFile(), SftpConstants.SSH_FXP_READDIR, "", 
followLinks);
         int nb = 0;
         Map<String, Path> entries = new TreeMap<>(Comparator.naturalOrder());
-        while ((dir.isSendDot() || dir.isSendDotDot() || dir.hasNext()) && 
(buffer.wpos() < maxSize)) {
-            if (dir.isSendDot()) {
-                writeDirEntry(id, dir, entries, buffer, nb, dir.getFile(), 
".", options);
-                dir.markDotSent(); // do not send it again
-            } else if (dir.isSendDotDot()) {
-                Path dirPath = dir.getFile();
-                Path parentPath = dirPath.getParent();
-                if (parentPath != null) {
-                    writeDirEntry(id, dir, entries, buffer, nb, parentPath, 
"..", options);
+        Predicate<DirectoryHandle> hasMore = d -> {
+            if (d.isWithDots()) {
+                return d.hasNext();
+            }
+            return d.isSendDot() || d.isSendDotDot() || d.hasNext();
+        };
+        while (hasMore.test(dir) && buffer.wpos() < maxSize) {
+            if (!dir.isWithDots()) {
+                if (dir.isSendDot()) {
+                    writeDirEntry(id, dir, entries, buffer, nb++, 
dir.getFile(), ".", options);
+                    dir.markDotSent(); // do not send it again
+                    continue;
+                } else if (dir.isSendDotDot()) {
+                    Path dirPath = dir.getFile();
+                    Path parentPath = dirPath.getParent();
+                    if (parentPath != null) {
+                        writeDirEntry(id, dir, entries, buffer, nb++, 
parentPath, "..", options);
+                    }
+                    dir.markDotDotSent(); // do not send it again
+                    continue;
+                }
+            }
+            Path f = dir.next();
+            String shortName = f.getFileName().toString();
+            if (".".equals(shortName)) {
+                if (!dir.isSendDot()) {
+                    continue;
                 }
-                dir.markDotDotSent(); // do not send it again
+                dir.markDotSent();
+            } else if ("..".equals(shortName)) {
+                if (!dir.isSendDotDot()) {
+                    continue;
+                }
+                dir.markDotDotSent();
             } else {
-                Path f = dir.next();
-                String shortName = getShortName(f);
-                if (f instanceof WithFileAttributes) {
-                    SftpClient.Attributes attributes = ((WithFileAttributes) 
f).getAttributes();
-                    if (attributes != null) {
-                        entries.put(shortName, f);
-                        writeDirEntry(session, id, buffer, nb, f, shortName, 
attributes);
-                        nb++;
-                        continue;
-                    }
+                shortName = getShortName(f);
+            }
+            if (f instanceof WithFileAttributes) {
+                SftpClient.Attributes attributes = ((WithFileAttributes) 
f).getAttributes();
+                if (attributes != null) {
+                    entries.put(shortName, f);
+                    writeDirEntry(session, id, buffer, nb++, f, shortName, 
attributes);
+                    continue;
                 }
-                writeDirEntry(id, dir, entries, buffer, nb, f, shortName, 
options);
             }
-
-            nb++;
+            writeDirEntry(id, dir, entries, buffer, nb++, f, shortName, 
options);
         }
 
         SftpEventListener listener = getSftpEventListenerProxy();
@@ -2283,7 +2303,10 @@ public abstract class AbstractSftpSubsystemHelper
         accessor.putRemoteFileName(this, f, buffer, shortName, true);
 
         if (version == SftpConstants.SFTP_V3) {
-            String longName = getLongName(f, shortName, attributes);
+            String longName = attributes.longName();
+            if (GenericUtils.isEmpty(longName)) {
+                longName = getLongName(f, shortName, attributes);
+            }
             accessor.putRemoteFileName(this, f, buffer, longName, false);
 
             if (log.isTraceEnabled()) {
diff --git 
a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/DirectoryHandle.java 
b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/DirectoryHandle.java
index a6a55a674..907608fa0 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/DirectoryHandle.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/server/DirectoryHandle.java
@@ -23,10 +23,15 @@ import java.nio.file.DirectoryStream;
 import java.nio.file.Path;
 import java.util.Iterator;
 
+import org.apache.sshd.sftp.client.fs.impl.SftpUtils;
+
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public class DirectoryHandle extends Handle implements Iterator<Path> {
+
+    private final boolean withDots;
+
     private boolean done;
     private boolean sendDotDot = true;
     private boolean sendDot = true;
@@ -39,8 +44,19 @@ public class DirectoryHandle extends Handle implements 
Iterator<Path> {
 
         SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
         signalHandleOpening();
-        ds = accessor.openDirectory(subsystem, this, dir, handle);
-
+        // If the file system itself is a SftpFileSystem, then we do actually 
get "." and ".." entries from the upstream server.
+        // In that case we want the DirectoryStream to report them so that we 
can have the attributes directly.
+        SftpUtils.DIRECTORY_WITH_DOTS.set(Boolean.TRUE);
+        boolean haveDots = false;
+        try {
+            ds = accessor.openDirectory(subsystem, this, dir, handle);
+            // A non-Sftp file system won't know about this ThreadLocal, and 
will thus not change it. Our SftpFileSystem
+            // resets the value to null, if it can deliver "." and ".." 
entries.
+            haveDots = SftpUtils.DIRECTORY_WITH_DOTS.get() == null;
+        } finally {
+            SftpUtils.DIRECTORY_WITH_DOTS.remove();
+        }
+        withDots = haveDots;
         Path parent = dir.getParent();
         if (parent == null) {
             sendDotDot = false; // if no parent then no need to send ".."
@@ -55,6 +71,10 @@ public class DirectoryHandle extends Handle implements 
Iterator<Path> {
         }
     }
 
+    public boolean isWithDots() {
+        return withDots;
+    }
+
     public boolean isDone() {
         return done;
     }
diff --git 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
index bd414b75e..ad45d9804 100644
--- 
a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
+++ 
b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpFileSystemTest.java
@@ -68,6 +68,7 @@ import org.apache.sshd.common.channel.ChannelListener;
 import org.apache.sshd.common.file.FileSystemFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.session.SessionListener;
 import org.apache.sshd.common.util.ExceptionUtils;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.MapEntryUtils.MapBuilder;
@@ -94,6 +95,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.MethodOrderer.MethodName;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -466,6 +469,122 @@ public class SftpFileSystemTest extends 
AbstractSftpFilesSystemSupport {
         }
     }
 
+    @ParameterizedTest(name = "Versions:{0}/{1}")
+    @CsvSource({ SftpConstants.SFTP_V3 + "," + SftpConstants.SFTP_V3, 
SftpConstants.SFTP_V3 + "," + SftpConstants.SFTP_V6 })
+    void fileSystemListDirIndirectVersion(int intermediaryVersion, int 
upstreamVersion) throws Exception {
+        SessionListener upstreamSelector = new SessionListener() {
+            @Override
+            public void sessionCreated(Session session) {
+                SftpModuleProperties.SFTP_VERSION.set(session, 
upstreamVersion);
+            }
+        };
+        sshd.addSessionListener(upstreamSelector);
+        SftpSubsystemFactory factory = (SftpSubsystemFactory) 
NamedResource.findByName(SftpConstants.SFTP_SUBSYSTEM_NAME,
+                String.CASE_INSENSITIVE_ORDER, sshd.getSubsystemFactories());
+        AtomicInteger readDirCount = new AtomicInteger();
+        factory.addSftpEventListener(new SftpEventListener() {
+            @Override
+            public void received(ServerSession session, int type, int id) 
throws IOException {
+                if (type == SftpConstants.SSH_FXP_READDIR) {
+                    readDirCount.getAndIncrement();
+                }
+            }
+        });
+        Path targetPath = detectTargetFolder();
+        Path lclSftp = CommonTestSupportUtils.resolve(targetPath, 
SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(),
+                getCurrentTestName());
+        CommonTestSupportUtils.deleteRecursive(lclSftp);
+
+        FileSystem secondHop = 
FileSystems.newFileSystem(createDefaultFileSystemURI(), defaultOptions());
+        assertTrue(secondHop instanceof SftpFileSystem, "Not an 
SftpFileSystem");
+        SshServer intermediary = createIntermediaryServer(secondHop);
+        intermediary.addSessionListener(new SessionListener() {
+            @Override
+            public void sessionCreated(Session session) {
+                SftpModuleProperties.SFTP_VERSION.set(session, 
intermediaryVersion);
+            }
+        });
+
+        try (FileSystem fs = FileSystems.newFileSystem(
+                createFileSystemURI(getCurrentTestName(), 
intermediary.getPort(), Collections.emptyMap()), defaultOptions())) {
+            assertTrue(fs instanceof SftpFileSystem, "Not an SftpFileSystem");
+
+            Path parentPath = targetPath.getParent();
+            Path clientFolder = lclSftp.resolve("client");
+            assertHierarchyTargetFolderExists(clientFolder);
+            // Create files
+            final int numberOfFiles = 200;
+            for (int i = 1; i <= numberOfFiles; i++) {
+                Path localFile = clientFolder.resolve("file" + i + ".txt");
+                Files.createFile(localFile);
+            }
+            String remDirPath = 
CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder);
+            Path remoteDir = fs.getPath(remDirPath);
+            assertHierarchyTargetFolderExists(remoteDir);
+
+            SftpPath sftpPath = (SftpPath) remoteDir;
+
+            // Read from the intermediary server
+            List<String> items = new ArrayList<>();
+            try (SftpClient client = sftpPath.getFileSystem().getClient(); 
CloseableHandle dir = client.openDir(remDirPath)) {
+                for (SftpClient.DirEntry entry : client.listDir(dir)) {
+                    assertNotNull(entry);
+                    String longName = entry.getLongFilename();
+                    assertNotNull(longName);
+                    items.add(longName);
+                    assertFalse(longName.contains("OWNER@"));
+                    assertFalse(longName.contains("GROUP@"));
+                    String[] parts = longName.split("\\s+");
+                    assertTrue(parts.length > 4);
+                    long size = Long.parseLong(parts[4]);
+                    long attrSize = entry.getAttributes().getSize();
+                    assertEquals(size, attrSize);
+                }
+            }
+            assertEquals(numberOfFiles + 2, items.size()); // . and ..
+            assertTrue(readDirCount.get() > 0, "Upstream server not called");
+
+            // Read directly from the upstream server
+            if (upstreamVersion == SftpConstants.SFTP_V3) {
+                List<String> upstreamItems = new ArrayList<>();
+                try (SftpClient client = ((SftpFileSystem) 
secondHop).getClient();
+                     CloseableHandle dir = client.openDir(remDirPath)) {
+                    for (SftpClient.DirEntry entry : client.listDir(dir)) {
+                        assertNotNull(entry);
+                        String longName = entry.getLongFilename();
+                        upstreamItems.add(longName);
+                        String[] parts = longName.split("\\s+");
+                        assertTrue(parts.length > 4);
+                        long size = Long.parseLong(parts[4]);
+                        long attrSize = entry.getAttributes().getSize();
+                        assertEquals(size, attrSize);
+                    }
+                }
+                assertEquals(numberOfFiles + 2, upstreamItems.size()); // . 
and ..
+                assertEquals(items.toString(), upstreamItems.toString());
+            } else {
+                int i = 0;
+                try (SftpClient client = ((SftpFileSystem) 
secondHop).getClient();
+                     CloseableHandle dir = client.openDir(remDirPath)) {
+                    for (SftpClient.DirEntry entry : client.listDir(dir)) {
+                        i++;
+                        assertNotNull(entry);
+                        assertNull(entry.getLongFilename());
+                    }
+                }
+                assertEquals(numberOfFiles + 2, i); // . and ..
+            }
+        } finally {
+            sshd.removeSessionListener(upstreamSelector);
+            if (secondHop != null) {
+                secondHop.close();
+            }
+            if (intermediary != null) {
+                intermediary.stop(true);
+            }
+        }
+    }
+
     // SSHD-1220
     @Test
     void attributeCache() throws Exception {

Reply via email to