You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2016/01/11 07:28:55 UTC
mina-sshd git commit: [SSHD-623] Add 'end-of-list' indicator for SFTP
SSH_FXP_NAME and SSH_FXP_READDIR responses
Repository: mina-sshd
Updated Branches:
refs/heads/master 2bd4f5709 -> 66d53ac5a
[SSHD-623] Add 'end-of-list' indicator for SFTP SSH_FXP_NAME and SSH_FXP_READDIR responses
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/66d53ac5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/66d53ac5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/66d53ac5
Branch: refs/heads/master
Commit: 66d53ac5aec0e6c7e6a37923e48c06daf99e61b1
Parents: 2bd4f57
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jan 11 08:28:45 2016 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jan 11 08:28:45 2016 +0200
----------------------------------------------------------------------
.../subsystem/sftp/AbstractSftpClient.java | 92 +++++++++---
.../sshd/client/subsystem/sftp/SftpClient.java | 57 ++++++++
.../subsystem/sftp/SftpDirEntryIterator.java | 18 ++-
.../sshd/common/subsystem/sftp/SftpHelper.java | 80 +++++++++++
.../server/subsystem/sftp/SftpSubsystem.java | 143 +++++++++++--------
.../client/subsystem/sftp/SftpVersionsTest.java | 50 +++++++
6 files changed, 361 insertions(+), 79 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
index 53e29b2..ac37b31 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.subsystem.AbstractSubsystemClient;
import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
@@ -107,11 +108,6 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
}
@Override
- public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
- return read(handle, fileOffset, dst, 0, dst.length);
- }
-
- @Override
public OutputStream write(String path) throws IOException {
return write(path, DEFAULT_WRITE_BUFFER_SIZE);
}
@@ -367,10 +363,13 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
if (version == SftpConstants.SFTP_V3) {
longName = buffer.getString();
}
+
Attributes attrs = readAttributes(buffer);
+ Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
+ // TODO decide what to do if not-null and not TRUE
if (log.isTraceEnabled()) {
- log.trace("checkOneName({})[id={}] ({})[{}]: {}",
- getClientChannel(), id, name, longName, attrs);
+ log.trace("checkOneName({})[id={}] ({})[{}] eol={}: {}",
+ getClientChannel(), id, name, longName, indicator, attrs);
}
return name;
}
@@ -766,8 +765,27 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
checkStatus(SftpConstants.SSH_FXP_RENAME, buffer);
}
- @Override
+ @Override // TODO make this a default method in Java 8
+ public int read(Handle handle, long fileOffset, byte[] dst) throws IOException {
+ return read(handle, fileOffset, dst, null);
+ }
+
+ @Override // TODO make this a default method in Java 8
+ public int read(Handle handle, long fileOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
+ return read(handle, fileOffset, dst, 0, dst.length, eofSignalled);
+ }
+
+ @Override // TODO make this a default method in Java 8
public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
+ return read(handle, fileOffset, dst, dstOffset, len, null);
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
+
if (!isOpen()) {
throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
}
@@ -777,22 +795,38 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
buffer.putBytes(id);
buffer.putLong(fileOffset);
buffer.putInt(len);
- return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst);
+ return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled);
}
- protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst) throws IOException {
+ protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
int reqId = send(cmd, request);
Buffer response = receive(reqId);
- return checkData(response, dstOffset, dst);
+ return checkData(response, dstOffset, dst, eofSignalled);
}
- protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
+ protected int checkData(Buffer buffer, int dstoff, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException {
+ if (eofSignalled != null) {
+ eofSignalled.set(null);
+ }
+
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
if (type == SftpConstants.SSH_FXP_DATA) {
int len = buffer.getInt();
buffer.getRawBytes(dst, dstoff, len);
+ Boolean indicator = SftpHelper.getEndOfFileIndicatorValue(buffer, getVersion());
+ if (log.isTraceEnabled()) {
+ log.trace("checkData({}][id={}] offset={}, len={}, EOF={}",
+ getClientChannel(), id, dstoff, len, indicator);
+ }
+ if (eofSignalled != null) {
+ eofSignalled.set(indicator);
+ }
+
return len;
}
@@ -903,8 +937,16 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
return handle;
}
- @Override
+ @Override // TODO in JDK-8 make this a default method
public List<DirEntry> readDir(Handle handle) throws IOException {
+ return readDir(handle, null);
+ }
+
+ @Override
+ public List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException {
+ if (eolIndicator != null) {
+ eolIndicator.set(null); // assume unknown information
+ }
if (!isOpen()) {
throw new IOException("readDir(" + handle + ") client is closed");
}
@@ -912,28 +954,44 @@ public abstract class AbstractSftpClient extends AbstractSubsystemClient impleme
byte[] id = handle.getIdentifier();
Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */, false);
buffer.putBytes(id);
- return checkDir(receive(send(SftpConstants.SSH_FXP_READDIR, buffer)));
+ return checkDir(receive(send(SftpConstants.SSH_FXP_READDIR, buffer)), eolIndicator);
}
- protected List<DirEntry> checkDir(Buffer buffer) throws IOException {
+ protected List<DirEntry> checkDir(Buffer buffer, AtomicReference<Boolean> eolIndicator) throws IOException {
+ if (eolIndicator != null) {
+ eolIndicator.set(null); // assume unknown
+ }
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
if (type == SftpConstants.SSH_FXP_NAME) {
int len = buffer.getInt();
+ int version = getVersion();
+ ClientChannel channel = getClientChannel();
+ if (log.isDebugEnabled()) {
+ log.debug("checkDir({}}[id={}] reading {} entries", channel, id, len);
+ }
List<DirEntry> entries = new ArrayList<DirEntry>(len);
for (int i = 0; i < len; i++) {
String name = buffer.getString();
- int version = getVersion();
String longName = (version == SftpConstants.SFTP_V3) ? buffer.getString() : null;
Attributes attrs = readAttributes(buffer);
if (log.isTraceEnabled()) {
log.trace("checkDir({})[id={}][{}] ({})[{}]: {}",
- getClientChannel(), id, i, name, longName, attrs);
+ channel, id, i, name, longName, attrs);
}
entries.add(new DirEntry(name, longName, attrs));
}
+
+ Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version);
+ if (eolIndicator != null) {
+ eolIndicator.set(indicator);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("checkDir({}}[id={}] read count={}, eol={}", channel, entries.size(), indicator);
+ }
return entries;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index c63c95a..fa439f7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.client.subsystem.SubsystemClient;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
@@ -485,10 +486,53 @@ public interface SftpClient extends SubsystemClient {
void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException;
+ /**
+ * Reads data from the open (file) handle
+ *
+ * @param handle The file {@link Handle} to read from
+ * @param fileOffset The file offset to read from
+ * @param dst The destination buffer
+ * @return Number of read bytes - {@code -1} if EOF reached
+ * @throws IOException If failed to read the data
+ * @see #read(Handle, long, byte[], int, int)
+ */
int read(Handle handle, long fileOffset, byte[] dst) throws IOException;
+ /**
+ * Reads data from the open (file) handle
+ *
+ * @param handle The file {@link Handle} to read from
+ * @param fileOffset The file offset to read from
+ * @param dst The destination buffer
+ * @param eofSignalled If not {@code null} then upon return holds a value indicating
+ * whether EOF was reached due to the read. If {@code null} indicator
+ * value then this indication is not available
+ * @return Number of read bytes - {@code -1} if EOF reached
+ * @throws IOException If failed to read the data
+ * @see #read(Handle, long, byte[], int, int, AtomicReference)
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.3">SFTP v6 - section 9.3</A>
+ */
+ int read(Handle handle, long fileOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException;
+
int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException;
+ /**
+ * Reads data from the open (file) handle
+ *
+ * @param handle The file {@link Handle} to read from
+ * @param fileOffset The file offset to read from
+ * @param dst The destination buffer
+ * @param dstOffset Offset in destination buffer to place the read data
+ * @param len Available destination buffer size to read
+ * @param eofSignalled If not {@code null} then upon return holds a value indicating
+ * whether EOF was reached due to the read. If {@code null} indicator
+ * value then this indication is not available
+ * @return Number of read bytes - {@code -1} if EOF reached
+ * @throws IOException If failed to read the data
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.3">SFTP v6 - section 9.3</A>
+ */
+ int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException;
+
void write(Handle handle, long fileOffset, byte[] src) throws IOException;
void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException;
@@ -511,6 +555,19 @@ public interface SftpClient extends SubsystemClient {
*/
List<DirEntry> readDir(Handle handle) throws IOException;
+ /**
+ * @param handle Directory {@link Handle} to read from
+ * @return A {@link List} of entries - {@code null} to indicate no more entries
+ * @param eolIndicator An indicator that can be used to get information
+ * whether end of list has been reached - ignored if {@code null}. Upon
+ * return, set value indicates whether all entries have been exhausted - a {@code null}
+ * value means that this information cannot be provided and another call to
+ * {@code readDir} is necessary in order to verify that no more entries are pending
+ * @throws IOException If failed to access the remote site
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4">SFTP v6 - section 9.4</A>
+ */
+ List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException;
+
String canonicalPath(String path) throws IOException;
Attributes stat(String path) throws IOException;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirEntryIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirEntryIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirEntryIterator.java
index 72cdaca..c828015 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirEntryIterator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirEntryIterator.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.nio.channels.Channel;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
@@ -37,6 +38,7 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SftpDirEntryIterator extends AbstractLoggingBean implements Iterator<DirEntry>, Channel {
+ private final AtomicReference<Boolean> eolIndicator = new AtomicReference<>();
private final SftpClient client;
private final String dirPath;
private CloseableHandle dirHandle;
@@ -127,10 +129,20 @@ public class SftpDirEntryIterator extends AbstractLoggingBean implements Iterato
protected List<DirEntry> load(CloseableHandle handle) {
try {
- List<DirEntry> entries = client.readDir(handle);
- if (entries == null) {
+ // check if previous call yielded an end-of-list indication
+ Boolean eolReached = eolIndicator.getAndSet(null);
+ if ((eolReached != null) && eolReached.booleanValue()) {
if (log.isTraceEnabled()) {
- log.trace("load(" + getPath() + ") exhausted all entries");
+ log.trace("load({}) exhausted all entries on previous call", getPath());
+ }
+ return null;
+ }
+
+ List<DirEntry> entries = client.readDir(handle, eolIndicator);
+ eolReached = eolIndicator.get();
+ if ((entries == null) || ((eolReached != null) && eolReached.booleanValue())) {
+ if (log.isTraceEnabled()) {
+ log.trace("load({}) exhausted all entries - EOL={}", getPath(), eolReached);
}
close();
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java
index d03cac0..3f97a6f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpHelper.java
@@ -48,6 +48,8 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.ValidateUtils;
@@ -62,12 +64,90 @@ import org.apache.sshd.server.subsystem.sftp.UnixDateFormat;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class SftpHelper {
+ /**
+ * Used to control whether to append the end-of-list indicator for
+ * SSH_FXP_NAME responses via {@link #indicateEndOfNamesList(Buffer, int, PropertyResolver, Boolean)}
+ * call, as indicated by <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4">SFTP v6 - section 9.4</A>
+ */
+ public static final String APPEND_END_OF_LIST_INDICATOR = "sftp-append-eol-indicator";
+
+ /**
+ * Default value for {@link #APPEND_END_OF_LIST_INDICATOR} if none configured
+ */
+ public static final boolean DEFAULT_APPEND_END_OF_LIST_INDICATOR = true;
private SftpHelper() {
throw new UnsupportedOperationException("No instance allowed");
}
/**
+ * Retrieves the end-of-file indicator for {@code SSH_FXP_DATA} responses, provided
+ * the version is at least 6, and the buffer has enough available data
+ *
+ * @param buffer The {@link Buffer} to retrieve the data from
+ * @param version The SFTP version being used
+ * @return The indicator value - {@code null} if none retrieved
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.3">SFTP v6 - section 9.3</A>
+ */
+ public static Boolean getEndOfFileIndicatorValue(Buffer buffer, int version) {
+ return (version < SftpConstants.SFTP_V6) || (buffer.available() < 1) ? null : Boolean.valueOf(buffer.getBoolean());
+ }
+
+ /**
+ * Retrieves the end-of-list indicator for {@code SSH_FXP_NAME} responses, provided
+ * the version is at least 6, and the buffer has enough available data
+ *
+ * @param buffer The {@link Buffer} to retrieve the data from
+ * @param version The SFTP version being used
+ * @return The indicator value - {@code null} if none retrieved
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4">SFTP v6 - section 9.4</A>
+ * @see #indicateEndOfNamesList(Buffer, int, PropertyResolver, Boolean)
+ */
+ public static Boolean getEndOfListIndicatorValue(Buffer buffer, int version) {
+ return (version < SftpConstants.SFTP_V6) || (buffer.available() < 1) ? null : Boolean.valueOf(buffer.getBoolean());
+ }
+
+ /**
+ * Appends the end-of-list={@code TRUE} indicator for {@code SSH_FXP_NAME} responses, provided
+ * the version is at least 6 and the feature is enabled
+ *
+ * @param buffer The {@link Buffer} to append the indicator
+ * @param version The SFTP version being used
+ * @param resolver The {@link PropertyResolver} to query whether to enable the feature
+ * @return The actual indicator value used - {@code null} if none appended
+ * @see #indicateEndOfNamesList(Buffer, int, PropertyResolver, Boolean)
+ */
+ public static Boolean indicateEndOfNamesList(Buffer buffer, int version, PropertyResolver resolver) {
+ return indicateEndOfNamesList(buffer, version, resolver, Boolean.TRUE);
+ }
+
+ /**
+ * Appends the end-of-list indicator for {@code SSH_FXP_NAME} responses, provided the version
+ * is at least 6, the feature is enabled and the indicator value is not {@code null}
+ *
+ * @param buffer The {@link Buffer} to append the indicator
+ * @param version The SFTP version being used
+ * @param resolver The {@link PropertyResolver} to query whether to enable the feature
+ * @param indicatorValue The indicator value - {@code null} means don't append the indicator
+ * @return The actual indicator value used - {@code null} if none appended
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4">SFTP v6 - section 9.4</A>
+ * @see #APPEND_END_OF_LIST_INDICATOR
+ * @see #DEFAULT_APPEND_END_OF_LIST_INDICATOR
+ */
+ public static Boolean indicateEndOfNamesList(Buffer buffer, int version, PropertyResolver resolver, Boolean indicatorValue) {
+ if ((version < SftpConstants.SFTP_V6) || (indicatorValue == null)) {
+ return null;
+ }
+
+ if (!PropertyResolverUtils.getBooleanProperty(resolver, APPEND_END_OF_LIST_INDICATOR, DEFAULT_APPEND_END_OF_LIST_INDICATOR)) {
+ return null;
+ }
+
+ buffer.putBoolean(indicatorValue.booleanValue());
+ return indicatorValue;
+ }
+
+ /**
* Writes a file / folder's attributes to a buffer
*
* @param buffer The target {@link Buffer}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 59c5098..a92a64a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -1536,10 +1536,19 @@ public class SftpSubsystem
*/
result = doRealPathV345(id, path, options);
} else {
- // see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13 section 8.9
- int control = 0;
+ /*
+ * See https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
+ *
+ * This field is optional, and if it is not present in the packet, it
+ * is assumed to be SSH_FXP_REALPATH_NO_CHECK.
+ */
+ int control = SftpConstants.SSH_FXP_REALPATH_NO_CHECK;
if (buffer.available() > 0) {
control = buffer.getUByte();
+ if (log.isDebugEnabled()) {
+ log.debug("doRealPath({}) - control=0x{} for path={}",
+ getServerSession(), Integer.toHexString(control), path);
+ }
}
Collection<String> extraPaths = new LinkedList<>();
@@ -1551,35 +1560,42 @@ public class SftpSubsystem
Path p = result.getFirst();
Boolean status = result.getSecond();
- if (control == SftpConstants.SSH_FXP_REALPATH_STAT_IF) {
- if (status == null) {
- attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
- } else if (status) {
- try {
- attrs = getAttributes(p, IoUtils.getLinkOptions(false));
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({}) - failed ({}) to retrieve attributes of {}: {}",
- getServerSession(), e.getClass().getSimpleName(), p, e.getMessage());
+ switch (control) {
+ case SftpConstants.SSH_FXP_REALPATH_STAT_IF:
+ if (status == null) {
+ attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
+ } else if (status) {
+ try {
+ attrs = getAttributes(p, IoUtils.getLinkOptions(false));
+ } catch (IOException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("doRealPath({}) - failed ({}) to retrieve attributes of {}: {}",
+ getServerSession(), e.getClass().getSimpleName(), p, e.getMessage());
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("doRealPath(" + getServerSession() + ")[" + p + "] attributes retrieval failure details", e);
+ }
}
- if (log.isTraceEnabled()) {
- log.trace("doRealPath(" + getServerSession() + ")[" + p + "] attributes retrieval failure details", e);
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("doRealPath({}) - dummy attributes for non-existing file: {}", getServerSession(), p);
}
}
- } else {
- if (log.isDebugEnabled()) {
- log.debug("doRealPath({}) - dummy attributes for non-existing file: {}",
- getServerSession(), p);
+ break;
+ case SftpConstants.SSH_FXP_REALPATH_STAT_ALWAYS:
+ if (status == null) {
+ attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
+ } else if (status) {
+ attrs = getAttributes(p, options);
+ } else {
+ throw new FileNotFoundException(p.toString());
}
- }
- } else if (control == SftpConstants.SSH_FXP_REALPATH_STAT_ALWAYS) {
- if (status == null) {
- attrs = handleUnknownStatusFileAttributes(p, SftpConstants.SSH_FILEXFER_ATTR_ALL, options);
- } else if (status) {
- attrs = getAttributes(p, options);
- } else {
- throw new FileNotFoundException(p.toString());
- }
+ break;
+ case SftpConstants.SSH_FXP_REALPATH_NO_CHECK:
+ break;
+ default:
+ log.warn("doRealPath({}) unknown control value 0x{} for path={}",
+ getServerSession(), Integer.toHexString(control), p);
}
}
} catch (IOException | RuntimeException e) {
@@ -1594,6 +1610,10 @@ public class SftpSubsystem
Path p = resolveFile(path);
int numExtra = GenericUtils.size(extraPaths);
if (numExtra > 0) {
+ if (log.isDebugEnabled()) {
+ log.debug("doRealPathV6({})[id={}] path={}, extra={}",
+ getServerSession(), id, path, extraPaths);
+ }
StringBuilder sb = new StringBuilder(GenericUtils.length(path) + numExtra * 8);
sb.append(path);
@@ -1795,13 +1815,15 @@ public class SftpSubsystem
int count = doReadDir(id, handle, dh, reply, PropertyResolverUtils.getIntProperty(getServerSession(), MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH));
BufferUtils.updateLengthPlaceholder(reply, lenPos, count);
- if (log.isDebugEnabled()) {
- log.debug("doReadDir({})({})[{}] - sent {} entries", getServerSession(), handle, h, count);
- }
+ ServerSession session = getServerSession();
if ((!dh.isSendDot()) && (!dh.isSendDotDot()) && (!dh.hasNext())) {
- // if no more files to send
dh.markDone();
}
+
+ Boolean indicator = SftpHelper.indicateEndOfNamesList(reply, getVersion(), session, Boolean.valueOf(dh.isDone()));
+ if (log.isDebugEnabled()) {
+ log.debug("doReadDir({})({})[{}] - seding {} entries - eol={}", session, handle, h, count, indicator);
+ }
} else {
// empty directory
dh.markDone();
@@ -2556,6 +2578,33 @@ public class SftpSubsystem
send(buffer);
}
+ protected void sendLink(Buffer buffer, int id, String link) throws IOException {
+ //in case we are running on Windows
+ String unixPath = link.replace(File.separatorChar, '/');
+ //normalize the given path, use *nix style separator
+ String normalizedPath = SelectorUtils.normalizePath(unixPath, "/");
+
+ buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
+ buffer.putInt(id);
+ buffer.putInt(1); // one response
+ buffer.putString(normalizedPath);
+
+ /*
+ * As per the spec (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.10):
+ *
+ * The server will respond with a SSH_FXP_NAME packet containing only
+ * one name and a dummy attributes value.
+ */
+ Map<String, Object> attrs = Collections.<String, Object>emptyMap();
+ if (version == SftpConstants.SFTP_V3) {
+ buffer.putString(SftpHelper.getLongName(normalizedPath, attrs));
+ }
+
+ writeAttrs(buffer, attrs);
+ SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession());
+ send(buffer);
+ }
+
protected void sendPath(Buffer buffer, int id, Path f, Map<String, ?> attrs) throws IOException {
buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
buffer.putInt(id);
@@ -2574,34 +2623,10 @@ public class SftpSubsystem
if (version == SftpConstants.SFTP_V3) {
f = resolveFile(normalizedPath);
buffer.putString(getLongName(f, getShortName(f), attrs));
- buffer.putInt(0); // no flags
- } else if (version >= SftpConstants.SFTP_V4) {
- writeAttrs(buffer, attrs);
- } else {
- throw new IllegalStateException("sendPath(" + f + ") unsupported version: " + version);
- }
- send(buffer);
- }
-
- protected void sendLink(Buffer buffer, int id, String link) throws IOException {
- //in case we are running on Windows
- String unixPath = link.replace(File.separatorChar, '/');
- buffer.putByte((byte) SftpConstants.SSH_FXP_NAME);
- buffer.putInt(id);
- buffer.putInt(1); // one response
-
- buffer.putString(unixPath);
- if (version == SftpConstants.SFTP_V3) {
- buffer.putString(unixPath);
}
- /*
- * As per the spec:
- *
- * The server will respond with a SSH_FXP_NAME packet containing only
- * one name and a dummy attributes value.
- */
- SftpHelper.writeAttrs(buffer, version, Collections.<String, Object>emptyMap());
+ writeAttrs(buffer, attrs);
+ SftpHelper.indicateEndOfNamesList(buffer, getVersion(), getServerSession());
send(buffer);
}
@@ -2740,7 +2765,7 @@ public class SftpSubsystem
}
protected void writeAttrs(Buffer buffer, Map<String, ?> attributes) throws IOException {
- SftpHelper.writeAttrs(buffer, version, attributes);
+ SftpHelper.writeAttrs(buffer, getVersion(), attributes);
}
protected Map<String, Object> getAttributes(Path file, LinkOption... options) throws IOException {
@@ -3225,7 +3250,7 @@ public class SftpSubsystem
}
protected Map<String, Object> readAttrs(Buffer buffer) throws IOException {
- return SftpHelper.readAttrs(buffer, version);
+ return SftpHelper.readAttrs(buffer, getVersion());
}
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/66d53ac5/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
index 2af1423..bd1e61e 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
@@ -38,10 +38,12 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
@@ -447,6 +449,54 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport {
assertEquals("Mismatched invocations count", numInvoked, numInvocations.get());
}
+ @Test // see SSHD-623
+ public void testEndOfListIndicator() throws Exception {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = session.createSftpClient(getTestedVersion())) {
+ AtomicReference<Boolean> eolIndicator = new AtomicReference<>();
+ int version = sftp.getVersion();
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, targetPath);
+
+ try (CloseableHandle handle = sftp.openDir(remotePath)) {
+ List<DirEntry> entries = sftp.readDir(handle, eolIndicator);
+ for (int index = 1; entries != null; entries = sftp.readDir(handle, eolIndicator), index++) {
+ Boolean value = eolIndicator.get();
+ if (version < SftpConstants.SFTP_V6) {
+ assertNull("Unexpected indicator value at iteration #" + index, value);
+ } else {
+ assertNotNull("No indicator returned at iteration #" + index, value);
+ if (value.booleanValue()) {
+ break;
+ }
+ }
+ eolIndicator.set(null); // make sure starting fresh
+ }
+
+ Boolean value = eolIndicator.get();
+ if (version < SftpConstants.SFTP_V6) {
+ assertNull("Unexpected end-of-list indication received at end of entries", value);
+ assertNull("Unexpected no last entries indication", entries);
+ } else {
+ assertNotNull("No end-of-list indication received at end of entries", value);
+ assertNotNull("No last received entries", entries);
+ assertTrue("Bad end-of-list value", value.booleanValue());
+ }
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getTestedVersion() + "]";