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 2015/07/05 16:48:49 UTC
[1/2] mina-sshd git commit: [SSHD-522] Expose ability to do SFTP
client version re-negotiation
Repository: mina-sshd
Updated Branches:
refs/heads/master f8a0e8129 -> 8e4476cc6
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
index 9523514..af4b0d2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -18,89 +18,25 @@
*/
package org.apache.sshd.client.subsystem.sftp;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_APPEND_DATA;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_DATA;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_DATA;
import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V4;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V5;
import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V6;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_APPEND;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREAT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_EXCL;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_READ;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNC;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_WRITE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_ATTRS;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_BLOCK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_CLOSE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_DATA;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSETSTAT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSTAT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_HANDLE;
import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_INIT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LINK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LSTAT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_MKDIR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_NAME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPEN;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPENDIR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READ;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READDIR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READLINK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REALPATH;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REMOVE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RMDIR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SETSTAT;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STAT;
import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STATUS;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SYMLINK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_UNBLOCK;
import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_VERSION;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_WRITE;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_EOF;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_OK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
-import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.attribute.FileTime;
+import java.io.StreamCorruptedException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.sshd.client.SftpException;
@@ -109,13 +45,12 @@ import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.FactoryManagerUtils;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+import org.apache.sshd.common.subsystem.sftp.extensions.VersionsParser.Versions;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.common.util.io.InputStreamWithChannel;
-import org.apache.sshd.common.util.io.OutputStreamWithChannel;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -366,828 +301,48 @@ public class DefaultSftpClient extends AbstractSftpClient {
}
}
- protected void checkStatus(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
-
- if (substatus != SSH_FX_OK) {
- throw new SftpException(substatus, msg);
- }
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected String checkHandle(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_HANDLE) {
- String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
- return handle;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected Attributes checkAttributes(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_ATTRS) {
- return readAttributes(buffer);
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected String checkOneName(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_NAME) {
- int len = buffer.getInt();
- if (len != 1) {
- throw new SshException("SFTP error: received " + len + " names instead of 1");
- }
- String name = buffer.getString(), longName = null;
- if (version == SFTP_V3) {
- longName = buffer.getString();
- }
- Attributes attrs = readAttributes(buffer);
- if (log.isTraceEnabled()) {
- log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
- }
- return name;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- protected Attributes readAttributes(Buffer buffer) throws IOException {
- Attributes attrs = new Attributes();
- int flags = buffer.getInt();
- if (version == SFTP_V3) {
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.flags.add(Attribute.Size);
- attrs.size = buffer.getLong();
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- attrs.flags.add(Attribute.UidGid);
- attrs.uid = buffer.getInt();
- attrs.gid = buffer.getInt();
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.flags.add(Attribute.Perms);
- attrs.perms = buffer.getInt();
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- attrs.flags.add(Attribute.AcModTime);
- attrs.atime = buffer.getInt();
- attrs.mtime = buffer.getInt();
- }
- } else if (version >= SFTP_V4) {
- attrs.type = buffer.getUByte();
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- attrs.flags.add(Attribute.Size);
- attrs.size = buffer.getLong();
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- attrs.flags.add(Attribute.OwnerGroup);
- attrs.owner = buffer.getString();
- attrs.group = buffer.getString();
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- attrs.flags.add(Attribute.Perms);
- attrs.perms = buffer.getInt();
- }
-
- // update the permissions according to the type
- switch (attrs.type) {
- case SSH_FILEXFER_TYPE_REGULAR:
- attrs.perms |= S_IFREG;
- break;
- case SSH_FILEXFER_TYPE_DIRECTORY:
- attrs.perms |= S_IFDIR;
- break;
- case SSH_FILEXFER_TYPE_SYMLINK:
- attrs.perms |= S_IFLNK;
- break;
- default: // do nothing
- }
-
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- attrs.flags.add(Attribute.AccessTime);
- attrs.accessTime = readTime(buffer, flags);
- attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
- }
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- attrs.flags.add(Attribute.CreateTime);
- attrs.createTime = readTime(buffer, flags);
- attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- attrs.flags.add(Attribute.ModifyTime);
- attrs.modifyTime = readTime(buffer, flags);
- attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
- }
- // TODO: acl
- } else {
- throw new IllegalStateException("readAttributes - unsupported version: " + version);
- }
- return attrs;
- }
-
- private FileTime readTime(Buffer buffer, int flags) {
- long secs = buffer.getLong();
- long millis = secs * 1000;
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- millis += buffer.getInt() / 1000000l;
- }
- return FileTime.from(millis, TimeUnit.MILLISECONDS);
- }
-
- protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
- if (version == SFTP_V3) {
- int flags = 0;
- for (Attribute a : attributes.flags) {
- switch (a) {
- case Size:
- flags |= SSH_FILEXFER_ATTR_SIZE;
- break;
- case UidGid:
- flags |= SSH_FILEXFER_ATTR_UIDGID;
- break;
- case Perms:
- flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AcModTime:
- flags |= SSH_FILEXFER_ATTR_ACMODTIME;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flags);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.size);
- }
- if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
- buffer.putInt(attributes.uid);
- buffer.putInt(attributes.gid);
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.perms);
- }
- if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
- buffer.putInt(attributes.atime);
- buffer.putInt(attributes.mtime);
- }
- } else if (version >= SFTP_V4) {
- int flags = 0;
- for (Attribute a : attributes.flags) {
- switch (a) {
- case Size:
- flags |= SSH_FILEXFER_ATTR_SIZE;
- break;
- case OwnerGroup:
- flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
- break;
- case Perms:
- flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
- break;
- case AccessTime:
- flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
- break;
- case ModifyTime:
- flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
- break;
- case CreateTime:
- flags |= SSH_FILEXFER_ATTR_CREATETIME;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(flags);
- buffer.putByte((byte) attributes.type);
- if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
- buffer.putLong(attributes.size);
- }
- if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
- buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
- buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
- }
- if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
- buffer.putInt(attributes.perms);
- }
- if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
- buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
- buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
- buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
- if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
- long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
- nanos = nanos % TimeUnit.SECONDS.toNanos(1);
- buffer.putInt((int) nanos);
- }
- buffer.putInt(attributes.atime);
- }
- // TODO: acl
- } else {
- throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
- }
- }
-
- @Override
- public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- if (version == SFTP_V3) {
- int mode = 0;
- for (OpenMode m : options) {
- switch (m) {
- case Read:
- mode |= SSH_FXF_READ;
- break;
- case Write:
- mode |= SSH_FXF_WRITE;
- break;
- case Append:
- mode |= SSH_FXF_APPEND;
- break;
- case Create:
- mode |= SSH_FXF_CREAT;
- break;
- case Truncate:
- mode |= SSH_FXF_TRUNC;
- break;
- case Exclusive:
- mode |= SSH_FXF_EXCL;
- break;
- default: // do nothing
- }
- }
- buffer.putInt(mode);
- } else {
- int mode = 0;
- int access = 0;
- if (options.contains(OpenMode.Read)) {
- access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Write)) {
- access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
- }
- if (options.contains(OpenMode.Append)) {
- access |= ACE4_APPEND_DATA;
- }
- if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
- mode |= SSH_FXF_CREATE_NEW;
- } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
- mode |= SSH_FXF_CREATE_TRUNCATE;
- } else if (options.contains(OpenMode.Create)) {
- mode |= SSH_FXF_OPEN_OR_CREATE;
- } else if (options.contains(OpenMode.Truncate)) {
- mode |= SSH_FXF_TRUNCATE_EXISTING;
- } else {
- mode |= SSH_FXF_OPEN_EXISTING;
- }
- if (version >= SFTP_V5) {
- buffer.putInt(access);
- }
- buffer.putInt(mode);
- }
- writeAttributes(buffer, new Attributes());
- return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
- }
-
- @Override
- public void close(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
- }
-
- @Override
- public void remove(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
- }
-
- @Override
- public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
- Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
- buffer.putString(oldPath);
- buffer.putString(newPath);
-
- int numOptions = GenericUtils.size(options);
- if (version >= SFTP_V5) {
- int opts = 0;
- if (numOptions > 0) {
- for (CopyMode opt : options) {
- switch (opt) {
- case Atomic:
- opts |= SSH_FXP_RENAME_ATOMIC;
- break;
- case Overwrite:
- opts |= SSH_FXP_RENAME_OVERWRITE;
- break;
- default: // do nothing
+ /**
+ * @param selector The {@link SftpVersionSelector} to use
+ * @return The selected version (may be same as current)
+ * @throws IOException If failed to negotiate
+ */
+ public int negotiateVersion(SftpVersionSelector selector) throws IOException {
+ int current = getVersion();
+ Set<Integer> available = GenericUtils.asSortedSet(Collections.singleton(Integer.valueOf(current)));
+ Map<String,?> parsed = getParsedServerExtensions();
+ Collection<String> extensions = ParserUtils.supportedExtensions(parsed);
+ if ((GenericUtils.size(extensions) > 0) && extensions.contains(SftpConstants.EXT_VERSELECT)) {
+ Versions vers = GenericUtils.isEmpty(parsed) ? null : (Versions) parsed.get(SftpConstants.EXT_VERSIONS);
+ Collection<String> reported = (vers == null) ? null : vers.versions;
+ if (GenericUtils.size(reported) > 0) {
+ for (String v : reported) {
+ if (!available.add(Integer.valueOf(v))) {
+ continue;
}
}
}
- buffer.putInt(opts);
- } else if (numOptions > 0) {
- throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
- + " - copy options can not be used with this SFTP version: " + options);
}
- checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
- }
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- buffer.putLong(fileOffset);
- buffer.putInt(len);
- return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
- }
-
- protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
-
- if (substatus == SSH_FX_EOF) {
- return -1;
- }
-
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_DATA) {
- int len = buffer.getInt();
- buffer.getRawBytes(dst, dstoff, len);
- return len;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ int selected = selector.selectVersion(current, new ArrayList<Integer>(available));
+ if (log.isDebugEnabled()) {
+ log.debug("negotiateVersion({}) {} -> {}", Integer.valueOf(current), available, Integer.valueOf(selected));
}
- }
- @Override
- public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
- // do some bounds checking first
- if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
- throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
- + " are non-negative values: file-offset=" + fileOffset
- + ", src-offset=" + srcoff + ", len=" + len);
- }
- if ((srcoff + len) > src.length) {
- throw new IllegalArgumentException("write(" + handle + ")"
- + " cannot read bytes " + srcoff + " to " + (srcoff + len)
- + " when array is only of length " + src.length);
- }
-
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- buffer.putLong(fileOffset);
- buffer.putBytes(src, srcoff, len);
- checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
- }
-
- @Override
- public void mkdir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path, StandardCharsets.UTF_8);
- buffer.putInt(0);
- if (version != SFTP_V3) {
- buffer.putByte((byte) 0);
- }
- checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
- }
-
- @Override
- public void rmdir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
- }
-
- @Override
- public CloseableHandle openDir(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
- }
-
- @Override
- public DirEntry[] readDir(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
- buffer.putString(handle.id);
- return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
- }
-
- protected DirEntry[] checkDir(Buffer buffer) throws IOException {
- int length = buffer.getInt();
- int type = buffer.getUByte();
- int id = buffer.getInt();
- if (type == SSH_FXP_STATUS) {
- int substatus = buffer.getInt();
- String msg = buffer.getString();
- String lang = buffer.getString();
- if (log.isTraceEnabled()) {
- log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
- }
- if (substatus == SSH_FX_EOF) {
- return null;
- }
- throw new SftpException(substatus, msg);
- } else if (type == SSH_FXP_NAME) {
- int len = buffer.getInt();
- DirEntry[] entries = new DirEntry[len];
- for (int i = 0; i < len; i++) {
- String name = buffer.getString();
- String longName = (version == SFTP_V3) ? buffer.getString() : null;
- Attributes attrs = readAttributes(buffer);
- if (log.isTraceEnabled()) {
- log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
- }
-
- entries[i] = new DirEntry(name, longName, attrs);
- }
- return entries;
- } else {
- throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
- }
- }
-
- @Override
- public String canonicalPath(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
- }
-
- @Override
- public Attributes stat(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
- }
-
- @Override
- public Attributes lstat(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
- }
-
- @Override
- public Attributes stat(Handle handle) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- if (version >= SFTP_V4) {
- buffer.putInt(SSH_FILEXFER_ATTR_ALL);
- }
- return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
- }
-
- @Override
- public void setStat(String path, Attributes attributes) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(path);
- writeAttributes(buffer, attributes);
- checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
- }
-
- @Override
- public void setStat(Handle handle, Attributes attributes) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- writeAttributes(buffer, attributes);
- checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
- }
-
- @Override
- public String readLink(String path) throws IOException {
- Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
- buffer.putString(path);
- return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
- }
-
- @Override
- public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
- Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
- if (version < SFTP_V6) {
- if (!symbolic) {
- throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
- }
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
- } else {
- buffer.putString(targetPath);
- buffer.putString(linkPath);
- buffer.putBoolean(symbolic);
- checkStatus(receive(send(SSH_FXP_LINK, buffer)));
- }
- }
-
- @Override
- public void lock(Handle handle, long offset, long length, int mask) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- buffer.putLong(offset);
- buffer.putLong(length);
- buffer.putInt(mask);
- checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
- }
-
- @Override
- public void unlock(Handle handle, long offset, long length) throws IOException {
- Buffer buffer = new ByteArrayBuffer();
- buffer.putString(handle.id);
- buffer.putLong(offset);
- buffer.putLong(length);
- checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
- }
-
- @Override
- public Iterable<DirEntry> readDir(final String path) throws IOException {
- return new Iterable<DirEntry>() {
- @Override
- public Iterator<DirEntry> iterator() {
- return new Iterator<DirEntry>() {
- private CloseableHandle handle;
- private DirEntry[] entries;
- private int index;
-
- {
- open();
- load();
- }
-
- @Override
- public boolean hasNext() {
- return (entries != null) && (index < entries.length);
- }
-
- @Override
- public DirEntry next() {
- DirEntry entry = entries[index++];
- if (index >= entries.length) {
- load();
- }
- return entry;
- }
-
- @SuppressWarnings("synthetic-access")
- private void open() {
- try {
- handle = openDir(path);
- if (log.isDebugEnabled()) {
- log.debug("readDir(" + path + ") handle=" + handle);
- }
- } catch (IOException e) {
- if (log.isDebugEnabled()) {
- log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
- }
- throw new RuntimeException(e);
- }
- }
-
- @SuppressWarnings("synthetic-access")
- private void load() {
- try {
- entries = readDir(handle);
- index = 0;
- if (entries == null) {
- handle.close();
- }
- } catch (IOException e) {
- entries = null;
- try {
- handle.close();
- } catch (IOException t) {
- if (log.isTraceEnabled()) {
- log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
- + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
- + ": " + t.getMessage());
- }
- }
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
- }
- };
- }
- };
- }
-
- @Override
- public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
- if (bufferSize < MIN_READ_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
+ if (selected == current) {
+ return current;
}
- return new InputStreamWithChannel() {
- private byte[] bb = new byte[1];
- private byte[] buffer = new byte[bufferSize];
- private int index;
- private int available;
- private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
- private long offset;
-
- @Override
- public boolean isOpen() {
- return (handle != null) && handle.isOpen();
- }
-
- @Override
- public int read() throws IOException {
- int read = read(bb, 0, 1);
- if (read > 0) {
- return bb[0];
- }
-
- return read;
- }
-
- @Override
- public int read(byte[] b, int off, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("read(" + path + ") stream closed");
- }
-
- int idx = off;
- while (len > 0) {
- if (index >= available) {
- available = DefaultSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
- if (available < 0) {
- if (idx == off) {
- return -1;
- } else {
- break;
- }
- }
- offset += available;
- index = 0;
- }
- if (index >= available) {
- break;
- }
- int nb = Math.min(len, available - index);
- System.arraycopy(buffer, index, b, idx, nb);
- index += nb;
- idx += nb;
- len -= nb;
- }
- return idx - off;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- try {
- handle.close();
- } finally {
- handle = null;
- }
- }
- }
- };
- }
-
- @Override
- public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
- if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
+ if (!available.contains(Integer.valueOf(selected))) {
+ throw new StreamCorruptedException("Selected version (" + selected + ") not part of available: " + available);
}
- return new OutputStreamWithChannel() {
- private byte[] bb = new byte[1];
- private byte[] buffer = new byte[bufferSize];
- private int index;
- private CloseableHandle handle = DefaultSftpClient.this.open(path, mode);
- private long offset;
-
- @Override
- public boolean isOpen() {
- return (handle != null) && handle.isOpen();
- }
-
- @Override
- public void write(int b) throws IOException {
- bb[0] = (byte) b;
- write(bb, 0, 1);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
- }
-
- do {
- int nb = Math.min(len, buffer.length - index);
- System.arraycopy(b, off, buffer, index, nb);
- index += nb;
- if (index == buffer.length) {
- flush();
- }
- off += nb;
- len -= nb;
- } while (len > 0);
- }
-
- @Override
- public void flush() throws IOException {
- if (!isOpen()) {
- throw new IOException("flush(" + path + ") stream is closed");
- }
-
- DefaultSftpClient.this.write(handle, offset, buffer, 0, index);
- offset += index;
- index = 0;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- try {
- try {
- if (index > 0) {
- flush();
- }
- } finally {
- handle.close();
- }
- } finally {
- handle = null;
- }
- }
- }
- };
+ String verVal = String.valueOf(selected);
+ Buffer buffer = new ByteArrayBuffer((Integer.SIZE / Byte.SIZE) + SftpConstants.EXT_VERSELECT.length() // extension name
+ + (Integer.SIZE / Byte.SIZE) + verVal.length());
+ buffer.putString(SftpConstants.EXT_VERSELECT);
+ buffer.putString(verVal);
+ checkStatus(receive(send(SftpConstants.SSH_FXP_EXTENDED, buffer)));
+ version = selected;
+ return selected;
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
index 050716f..a2af81f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -42,6 +42,7 @@ import org.apache.sshd.common.FactoryManagerUtils;
import org.apache.sshd.common.file.util.BaseFileSystem;
import org.apache.sshd.common.file.util.ImmutableList;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
public class SftpFileSystem extends BaseFileSystem<SftpPath> {
@@ -57,6 +58,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
private final String id;
private final ClientSession session;
+ private final SftpVersionSelector selector;
private final Queue<SftpClient> pool;
private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
private SftpPath defaultDir;
@@ -64,10 +66,11 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
private final List<FileStore> stores;
- public SftpFileSystem(SftpFileSystemProvider provider, String id, ClientSession session) throws IOException {
+ public SftpFileSystem(SftpFileSystemProvider provider, String id, ClientSession session, SftpVersionSelector selector) throws IOException {
super(provider);
this.id = id;
this.session = session;
+ this.selector = ValidateUtils.checkNotNull(selector, "No SFTP version selector provided", GenericUtils.EMPTY_OBJECT_ARRAY);
this.stores = Collections.unmodifiableList(Collections.<FileStore>singletonList(new SftpFileStore(id, this)));
this.pool = new LinkedBlockingQueue<>(FactoryManagerUtils.getIntProperty(session, POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
try (SftpClient client = getClient()) {
@@ -75,6 +78,10 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
}
}
+ public final SftpVersionSelector getSftpVersionSelector() {
+ return selector;
+ }
+
public final String getId() {
return id;
}
@@ -129,7 +136,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
while (wrapper == null) {
SftpClient client = pool.poll();
if (client == null) {
- client = session.createSftpClient();
+ client = session.createSftpClient(getSftpVersionSelector());
}
if (!client.isClosing()) {
wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index df0c1e8..23ae4f2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -110,11 +110,16 @@ public class SftpFileSystemProvider extends FileSystemProvider {
)));
private final SshClient client;
+ private final SftpVersionSelector selector;
private final Map<String, SftpFileSystem> fileSystems = new HashMap<String, SftpFileSystem>();
protected final Logger log;
public SftpFileSystemProvider() {
- this(null);
+ this((SshClient) null);
+ }
+
+ public SftpFileSystemProvider(SftpVersionSelector selector) {
+ this(null, selector);
}
/**
@@ -124,7 +129,12 @@ public class SftpFileSystemProvider extends FileSystemProvider {
* @see SshClient#setUpDefaultClient()
*/
public SftpFileSystemProvider(SshClient client) {
+ this(client, SftpVersionSelector.CURRENT);
+ }
+
+ public SftpFileSystemProvider(SshClient client, SftpVersionSelector selector) {
this.log = LoggerFactory.getLogger(getClass());
+ this.selector = ValidateUtils.checkNotNull(selector, "No SFTP version selector provided", GenericUtils.EMPTY_OBJECT_ARRAY);
if (client == null) {
// TODO: make this configurable using system properties
client = SshClient.setUpDefaultClient();
@@ -138,6 +148,10 @@ public class SftpFileSystemProvider extends FileSystemProvider {
return SftpConstants.SFTP_SUBSYSTEM_NAME;
}
+ public final SftpVersionSelector getSftpVersionSelector() {
+ return selector;
+ }
+
@Override // NOTE: co-variant return
public SftpFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided", GenericUtils.EMPTY_OBJECT_ARRAY);
@@ -168,7 +182,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
session.addPasswordIdentity(password);
session.auth().verify(FactoryManagerUtils.getLongProperty(env, AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME));
- fileSystem = new SftpFileSystem(this, id, session);
+ fileSystem = new SftpFileSystem(this, id, session, getSftpVersionSelector());
fileSystems.put(id, fileSystem);
} catch(Exception e) {
if (session != null) {
@@ -207,7 +221,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
throw new FileSystemAlreadyExistsException(id);
}
- fileSystem = new SftpFileSystem(this, id, session);
+ fileSystem = new SftpFileSystem(this, id, session, getSftpVersionSelector());
fileSystems.put(id, fileSystem);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
new file mode 100644
index 0000000..40d9ad7
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
@@ -0,0 +1,45 @@
+/*
+ * 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.client.subsystem.sftp;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpVersionSelector {
+ /**
+ * @param current The current version negotiated with the server
+ * @param available Extra versions available - may be empty and/or contain
+ * only the current one
+ * @return The new requested version - if same as current, then nothing is done
+ */
+ int selectVersion(int current, List<Integer> available);
+
+ /**
+ * An {@link SftpVersionSelector} that returns the current version
+ */
+ SftpVersionSelector CURRENT = new SftpVersionSelector() {
+ @Override
+ public int selectVersion(int current, List<Integer> available) {
+ return current;
+ }
+ };
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/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 3371904..0aa0572 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
@@ -178,7 +178,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
private final byte[] workBuf = new byte[Integer.SIZE / Byte.SIZE]; // TODO in JDK-8 use Integer.BYTES
private FileSystem fileSystem = FileSystems.getDefault();
private Path defaultDir = fileSystem.getPath(System.getProperty("user.dir"));
-
+ private long requestsCount;
private int version;
private final Map<String, byte[]> extensions = new HashMap<>();
private final Map<String, Handle> handles = new HashMap<>();
@@ -591,6 +591,10 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
log.warn("Unknown command type received: {}", Integer.valueOf(type));
sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is unsupported or not implemented");
}
+
+ if (type != SSH_FXP_INIT) {
+ requestsCount++;
+ }
}
protected void doExtended(Buffer buffer, int id) throws IOException {
@@ -774,17 +778,33 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
sendStatus(id, e);
}
}
-
+
protected void doVersionSelect(Buffer buffer, int id) throws IOException {
+ /*
+ * The 'version-select' MUST be the first request from the client to the
+ * server; if it is not, the server MUST fail the request and close the
+ * channel.
+ */
+ if (requestsCount > 0L) {
+ sendStatus(id, SSH_FX_FAILURE, "Version selection not the 1st request");
+ session.close(true);
+ return;
+ }
+
String proposed = buffer.getString();
Boolean result = validateProposedVersion(id, proposed);
+ /*
+ * "MUST then close the channel without processing any further requests"
+ */
if (result == null) { // response sent internally
+ session.close(true);
return;
} if (result.booleanValue()) {
version = Integer.parseInt(proposed);
sendStatus(id, SSH_FX_OK, "");
} else {
sendStatus(id, SSH_FX_FAILURE, "Unsupported version " + proposed);
+ session.close(true);
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
index aa7054f..3997c5b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
@@ -47,8 +47,11 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.file.root.RootedFileSystemProvider;
@@ -111,11 +114,7 @@ public class SftpFileSystemTest extends BaseTestSupport {
}
@Test
- public void testFileSystem() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
-
+ public void testFileSystem() throws Exception {
try(FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
new TreeMap<String,Object>() {
private static final long serialVersionUID = 1L; // we're not serializing it
@@ -125,106 +124,7 @@ public class SftpFileSystemTest extends BaseTestSupport {
put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE));
}
})) {
-
- Iterable<Path> rootDirs = fs.getRootDirectories();
- for (Path root : rootDirs) {
- String rootName = root.toString();
- try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
- for (Path child : ds) {
- System.out.append('\t').append('[').append(rootName).append("] ").println(child);
- }
- } catch(IOException | RuntimeException e) {
- // TODO on Windows one might get share problems for *.sys files
- // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
- // for now, Windows is less of a target so we are lenient with it
- if (OsUtils.isWin32()) {
- System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
- } else {
- throw e;
- }
- }
- }
-
- Path current = fs.getPath(".").toRealPath().normalize();
- System.out.append("CWD: ").println(current);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
- Path file1 = fs.getPath(remFile1Path);
- Files.createDirectories(file1.getParent());
-
- String expected="Hello world: " + getCurrentTestName();
- {
- Files.write(file1, expected.getBytes());
- String buf = new String(Files.readAllBytes(file1));
- assertEquals("Mismatched read test data", expected, buf);
- }
-
- String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
- Path file2 = fs.getPath(remFile2Path);
- String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
- Path file3 = fs.getPath(remFile3Path);
- try {
- Files.move(file2, file3);
- fail("Unexpected success in moving " + file2 + " => " + file3);
- } catch (NoSuchFileException e) {
- // expected
- }
-
- Files.write(file2, "h".getBytes());
- try {
- Files.move(file1, file2);
- fail("Unexpected success in moving " + file1 + " => " + file2);
- } catch (FileAlreadyExistsException e) {
- // expected
- }
- Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
- Files.move(file2, file1);
-
- Map<String, Object> attrs = Files.readAttributes(file1, "*");
- System.out.append(file1.toString()).append(" attributes: ").println(attrs);
-
- // TODO: symbolic links only work for absolute files
- // Path link = fs.getPath("target/sftp/client/test2.txt");
- // Files.createSymbolicLink(link, link.relativize(file));
- // assertTrue(Files.isSymbolicLink(link));
- // assertEquals("test.txt", Files.readSymbolicLink(link).toString());
-
- // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
- if (OsUtils.isUNIX()) {
- Path link = fs.getPath(remFile2Path);
- Path linkParent = link.getParent();
- Path relPath = linkParent.relativize(file1);
- Files.createSymbolicLink(link, relPath);
- assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
-
- Path symLink = Files.readSymbolicLink(link);
- assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
- Files.delete(link);
- }
-
- attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
- System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
-
- assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
-
- try (FileChannel channel = FileChannel.open(file1)) {
- try (FileLock lock = channel.lock()) {
- System.out.println("Locked " + lock.toString());
-
- try (FileChannel channel2 = FileChannel.open(file1)) {
- try (FileLock lock2 = channel2.lock()) {
- System.out.println("Locked " + lock2.toString());
- fail("Unexpected success in re-locking " + file1);
- } catch (OverlappingFileLockException e) {
- // expected
- }
- }
- }
- }
-
- Files.delete(file1);
+ testFileSystem(fs);
}
}
@@ -359,6 +259,161 @@ public class SftpFileSystemTest extends BaseTestSupport {
}
}
+ @Test
+ public void testSftpVersionSelector() throws Exception {
+ final AtomicInteger selected = new AtomicInteger(-1);
+ SftpVersionSelector selector = new SftpVersionSelector() {
+ @Override
+ public int selectVersion(int current, List<Integer> available) {
+ int numAvailable = GenericUtils.size(available);
+ Integer maxValue = null;
+ if (numAvailable == 1) {
+ maxValue = available.get(0);
+ } else {
+ for (Integer v : available) {
+ if (v.intValue() == current) {
+ continue;
+ }
+
+ if ((maxValue == null) || (maxValue.intValue() < v.intValue())) {
+ maxValue = v;
+ }
+ }
+ }
+
+ selected.set(maxValue.intValue());
+ return selected.get();
+ }
+ };
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try(FileSystem fs = session.createSftpFileSystem(selector)) {
+ assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+
+ try(SftpClient sftp = ((SftpFileSystem) fs).getClient()) {
+ assertEquals("Mismatched negotiated version", selected.get(), sftp.getVersion());
+ }
+
+ testFileSystem(fs);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+
+ }
+
+ private void testFileSystem(FileSystem fs) throws Exception {
+ Iterable<Path> rootDirs = fs.getRootDirectories();
+ for (Path root : rootDirs) {
+ String rootName = root.toString();
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+ for (Path child : ds) {
+ System.out.append('\t').append('[').append(rootName).append("] ").println(child);
+ }
+ } catch(IOException | RuntimeException e) {
+ // TODO on Windows one might get share problems for *.sys files
+ // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process"
+ // for now, Windows is less of a target so we are lenient with it
+ if (OsUtils.isWin32()) {
+ System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage());
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ Path current = fs.getPath(".").toRealPath().normalize();
+ System.out.append("CWD: ").println(current);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt"));
+ Path file1 = fs.getPath(remFile1Path);
+ Files.createDirectories(file1.getParent());
+
+ String expected="Hello world: " + getCurrentTestName();
+ {
+ Files.write(file1, expected.getBytes());
+ String buf = new String(Files.readAllBytes(file1));
+ assertEquals("Mismatched read test data", expected, buf);
+ }
+
+ String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt"));
+ Path file2 = fs.getPath(remFile2Path);
+ String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt"));
+ Path file3 = fs.getPath(remFile3Path);
+ try {
+ Files.move(file2, file3);
+ fail("Unexpected success in moving " + file2 + " => " + file3);
+ } catch (NoSuchFileException e) {
+ // expected
+ }
+
+ Files.write(file2, "h".getBytes());
+ try {
+ Files.move(file1, file2);
+ fail("Unexpected success in moving " + file1 + " => " + file2);
+ } catch (FileAlreadyExistsException e) {
+ // expected
+ }
+ Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING);
+ Files.move(file2, file1);
+
+ Map<String, Object> attrs = Files.readAttributes(file1, "*");
+ System.out.append(file1.toString()).append(" attributes: ").println(attrs);
+
+ // TODO: symbolic links only work for absolute files
+// Path link = fs.getPath("target/sftp/client/test2.txt");
+// Files.createSymbolicLink(link, link.relativize(file));
+// assertTrue(Files.isSymbolicLink(link));
+// assertEquals("test.txt", Files.readSymbolicLink(link).toString());
+
+ // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest
+ if (OsUtils.isUNIX()) {
+ Path link = fs.getPath(remFile2Path);
+ Path linkParent = link.getParent();
+ Path relPath = linkParent.relativize(file1);
+ Files.createSymbolicLink(link, relPath);
+ assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link));
+
+ Path symLink = Files.readSymbolicLink(link);
+ assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString());
+ Files.delete(link);
+ }
+
+ attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+ System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs);
+
+ assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1)));
+
+ try (FileChannel channel = FileChannel.open(file1)) {
+ try (FileLock lock = channel.lock()) {
+ System.out.println("Locked " + lock.toString());
+
+ try (FileChannel channel2 = FileChannel.open(file1)) {
+ try (FileLock lock2 = channel2.lock()) {
+ System.out.println("Locked " + lock2.toString());
+ fail("Unexpected success in re-locking " + file1);
+ } catch (OverlappingFileLockException e) {
+ // expected
+ }
+ }
+ }
+ }
+
+ Files.delete(file1);
+ }
+
private URI createDefaultFileSystemURI() {
return createFileSystemURI(getCurrentTestName());
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index b1e12db..9d90231 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -39,11 +39,13 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import org.apache.sshd.client.SftpException;
import org.apache.sshd.client.SshClient;
@@ -254,67 +256,8 @@ public class SftpTest extends BaseTestSupport {
session.addPasswordIdentity(getCurrentTestName());
session.auth().verify(5L, TimeUnit.SECONDS);
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
- String file = dir + "/" + getCurrentTestName() + ".txt";
-
try (SftpClient sftp = session.createSftpClient()) {
- sftp.mkdir(dir);
-
- try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
- byte[] d = "0123456789\n".getBytes();
- sftp.write(h, 0, d, 0, d.length);
- sftp.write(h, d.length, d, 0, d.length);
-
- SftpClient.Attributes attrs = sftp.stat(h);
- assertNotNull("No handle attributes", attrs);
- }
-
- try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
- SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
- assertNotNull("No dir entries", dirEntries);
- assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
- assertNull("Unexpected entry read", sftp.readDir(h));
- }
-
- sftp.remove(file);
-
- byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
- new Random(System.currentTimeMillis()).nextBytes(workBuf);
- try (OutputStream os = sftp.write(file)) {
- os.write(workBuf);
- }
-
- try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
- int readLen = is.read(workBuf);
- assertEquals("Mismatched read data length", workBuf.length, readLen);
-
- int i = is.read();
- assertEquals("Unexpected read past EOF", -1, i);
- }
-
- SftpClient.Attributes attributes = sftp.stat(file);
- assertTrue("Test file not detected as regular", attributes.isRegularFile());
-
- attributes = sftp.stat(dir);
- assertTrue("Test directory not reported as such", attributes.isDirectory());
-
- int nb = 0;
- for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
- assertNotNull("Unexpected null entry", entry);
- nb++;
- }
- assertEquals("Mismatched read dir entries", 1, nb);
-
- sftp.remove(file);
-
- sftp.rmdir(dir);
+ testClient(sftp);
}
} finally {
client.stop();
@@ -794,6 +737,113 @@ public class SftpTest extends BaseTestSupport {
}
@Test
+ public void testSftpVersionSelector() throws Exception {
+ final AtomicInteger selected = new AtomicInteger(-1);
+ SftpVersionSelector selector = new SftpVersionSelector() {
+ @Override
+ public int selectVersion(int current, List<Integer> available) {
+ int numAvailable = GenericUtils.size(available);
+ Integer maxValue = null;
+ if (numAvailable == 1) {
+ maxValue = available.get(0);
+ } else {
+ for (Integer v : available) {
+ if (v.intValue() == current) {
+ continue;
+ }
+
+ if ((maxValue == null) || (maxValue.intValue() < v.intValue())) {
+ maxValue = v;
+ }
+ }
+ }
+
+ selected.set(maxValue.intValue());
+ return selected.get();
+ }
+ };
+
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try(SftpClient sftp = session.createSftpClient(selector)) {
+ assertEquals("Mismatched negotiated version", selected.get(), sftp.getVersion());
+ testClient(sftp);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ private void testClient(SftpClient sftp) throws Exception {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder);
+ String file = dir + "/" + getCurrentTestName() + ".txt";
+
+ sftp.mkdir(dir);
+
+ try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) {
+ byte[] d = "0123456789\n".getBytes();
+ sftp.write(h, 0, d, 0, d.length);
+ sftp.write(h, d.length, d, 0, d.length);
+
+ SftpClient.Attributes attrs = sftp.stat(h);
+ assertNotNull("No handle attributes", attrs);
+ }
+
+ try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
+ SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
+ assertNotNull("No dir entries", dirEntries);
+ assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
+ assertNull("Unexpected entry read", sftp.readDir(h));
+ }
+
+ sftp.remove(file);
+
+ byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE];
+ new Random(System.currentTimeMillis()).nextBytes(workBuf);
+ try (OutputStream os = sftp.write(file)) {
+ os.write(workBuf);
+ }
+
+ try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) {
+ int readLen = is.read(workBuf);
+ assertEquals("Mismatched read data length", workBuf.length, readLen);
+
+ int i = is.read();
+ assertEquals("Unexpected read past EOF", -1, i);
+ }
+
+ SftpClient.Attributes attributes = sftp.stat(file);
+ assertTrue("Test file not detected as regular", attributes.isRegularFile());
+
+ attributes = sftp.stat(dir);
+ assertTrue("Test directory not reported as such", attributes.isDirectory());
+
+ int nb = 0;
+ for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
+ assertNotNull("Unexpected null entry", entry);
+ nb++;
+ }
+ assertEquals("Mismatched read dir entries", 1, nb);
+
+ sftp.remove(file);
+
+ sftp.rmdir(dir);
+ }
+
+ @Test
public void testCreateSymbolicLink() throws Exception {
// Do not execute on windows as the file system does not support symlinks
Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX());
[2/2] mina-sshd git commit: [SSHD-522] Expose ability to do SFTP
client version re-negotiation
Posted by lg...@apache.org.
[SSHD-522] Expose ability to do SFTP client version re-negotiation
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/8e4476cc
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/8e4476cc
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/8e4476cc
Branch: refs/heads/master
Commit: 8e4476cc6030ff147e87aed0d358f99799b58208
Parents: f8a0e81
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Sun Jul 5 17:48:40 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Sun Jul 5 17:48:40 2015 +0300
----------------------------------------------------------------------
.../sshd/client/session/ClientSession.java | 14 +
.../sshd/client/session/ClientSessionImpl.java | 24 +-
.../subsystem/sftp/AbstractSftpClient.java | 931 ++++++++++++++++++-
.../subsystem/sftp/DefaultSftpClient.java | 919 +-----------------
.../client/subsystem/sftp/SftpFileSystem.java | 11 +-
.../subsystem/sftp/SftpFileSystemProvider.java | 20 +-
.../subsystem/sftp/SftpVersionSelector.java | 45 +
.../server/subsystem/sftp/SftpSubsystem.java | 24 +-
.../subsystem/sftp/SftpFileSystemTest.java | 265 +++---
.../sshd/client/subsystem/sftp/SftpTest.java | 170 ++--
10 files changed, 1365 insertions(+), 1058 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index f74f7c3..9dc9ee2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -34,6 +34,7 @@ import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.scp.ScpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
import org.apache.sshd.common.SshdSocketAddress;
import org.apache.sshd.common.future.SshFuture;
import org.apache.sshd.common.scp.ScpTransferEventListener;
@@ -174,11 +175,24 @@ public interface ClientSession extends Session {
/**
* Create an SFTP client from this session.
+ * @return The created {@link SftpClient}
+ * @throws IOException if failed to create the client
*/
SftpClient createSftpClient() throws IOException;
+ /**
+ * @param selector The {@link SftpVersionSelector} to use - <B>Note:</B>
+ * if the server does not support versions re-negotiation then the
+ * selector will be presented with only one "choice" - the
+ * current version
+ * @return The created {@link SftpClient}
+ * @throws IOException If failed to create the client or re-negotiate
+ */
+ SftpClient createSftpClient(SftpVersionSelector selector) throws IOException;
FileSystem createSftpFileSystem() throws IOException;
+ FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException;
FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException;
+ FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException;
/**
* Start forwarding the given local address on the client to the given address on the server.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index ef1a156..f2ee39b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -45,6 +45,7 @@ import org.apache.sshd.client.subsystem.sftp.DefaultSftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.Service;
@@ -397,17 +398,34 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
@Override
public SftpClient createSftpClient() throws IOException {
- return new DefaultSftpClient(this);
+ return createSftpClient(SftpVersionSelector.CURRENT);
+ }
+
+ @Override
+ public SftpClient createSftpClient(SftpVersionSelector selector) throws IOException {
+ DefaultSftpClient client = new DefaultSftpClient(this);
+ client.negotiateVersion(selector);
+ return client;
}
@Override
public FileSystem createSftpFileSystem() throws IOException {
- return createSftpFileSystem(SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
+ return createSftpFileSystem(SftpVersionSelector.CURRENT);
+ }
+
+ @Override
+ public FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException {
+ return createSftpFileSystem(selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
}
@Override
public FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException {
- SftpFileSystemProvider provider = new SftpFileSystemProvider((org.apache.sshd.client.SshClient) factoryManager);
+ return createSftpFileSystem(SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize);
+ }
+
+ @Override
+ public FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException {
+ SftpFileSystemProvider provider = new SftpFileSystemProvider((org.apache.sshd.client.SshClient) factoryManager, selector);
SftpFileSystem fs = provider.newFileSystem(this);
fs.setReadBufferSize(readBufferSize);
fs.setWriteBufferSize(writeBufferSize);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e4476cc/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 1807cb4..e6a7ccf 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
@@ -19,21 +19,99 @@
package org.apache.sshd.client.subsystem.sftp;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_APPEND_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_READ_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_ATTRIBUTES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.ACE4_WRITE_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V3;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V4;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V5;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SFTP_V6;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_ALL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_CREATETIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SIZE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_ATTR_UIDGID;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_REGULAR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FILEXFER_TYPE_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_APPEND;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_NEW;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_CREATE_TRUNCATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_EXCL;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_OPEN_OR_CREATE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_TRUNCATE_EXISTING;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXF_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_ATTRS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_BLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_CLOSE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_DATA;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_FSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_HANDLE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_LSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_MKDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_NAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPEN;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_OPENDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READ;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_READLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REALPATH;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_REMOVE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_ATOMIC;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RENAME_OVERWRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_RMDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SETSTAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STAT;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_STATUS;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_SYMLINK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_UNBLOCK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FXP_WRITE;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_EOF;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.SSH_FX_OK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFDIR;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFLNK;
+import static org.apache.sshd.common.subsystem.sftp.SftpConstants.S_IFREG;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.attribute.FileTime;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import org.apache.sshd.client.SftpException;
import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory;
+import org.apache.sshd.common.SshException;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.InputStreamWithChannel;
+import org.apache.sshd.common.util.io.OutputStreamWithChannel;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
/**
@@ -157,6 +235,15 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
}
Map<String,byte[]> extensions = getServerExtensions();
+ Map<String,Object> parsed = getParsedServerExtensions(extensions);
+ return factory.create(this, this, extensions, parsed);
+ }
+
+ protected Map<String,Object> getParsedServerExtensions() {
+ return getParsedServerExtensions(getServerExtensions());
+ }
+
+ protected Map<String,Object> getParsedServerExtensions(Map<String,byte[]> extensions) {
Map<String,Object> parsed = parsedExtensionsHolder.get();
if (parsed == null) {
if ((parsed=ParserUtils.parse(extensions)) == null) {
@@ -164,7 +251,849 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
}
parsedExtensionsHolder.set(parsed);
}
+
+ return parsed;
+ }
- return factory.create(this, this, extensions, parsed);
+ protected void checkStatus(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkStatus(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ if (substatus != SSH_FX_OK) {
+ throw new SftpException(substatus, msg);
+ }
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected String checkHandle(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkHandle(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_HANDLE) {
+ String handle = ValidateUtils.checkNotNullAndNotEmpty(buffer.getString(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY);
+ return handle;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected Attributes checkAttributes(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkAttributes(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_ATTRS) {
+ return readAttributes(buffer);
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected String checkOneName(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneName(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ if (len != 1) {
+ throw new SshException("SFTP error: received " + len + " names instead of 1");
+ }
+ String name = buffer.getString(), longName = null;
+ int version = getVersion();
+ if (version == SFTP_V3) {
+ longName = buffer.getString();
+ }
+ Attributes attrs = readAttributes(buffer);
+ if (log.isTraceEnabled()) {
+ log.trace("checkOneName(id={}) ({})[{}]: {}", Integer.valueOf(id), name, longName, attrs);
+ }
+ return name;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ protected Attributes readAttributes(Buffer buffer) throws IOException {
+ Attributes attrs = new Attributes();
+ int flags = buffer.getInt();
+ int version = getVersion();
+ if (version == SFTP_V3) {
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.flags.add(Attribute.Size);
+ attrs.size = buffer.getLong();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ attrs.flags.add(Attribute.UidGid);
+ attrs.uid = buffer.getInt();
+ attrs.gid = buffer.getInt();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.flags.add(Attribute.Perms);
+ attrs.perms = buffer.getInt();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ attrs.flags.add(Attribute.AcModTime);
+ attrs.atime = buffer.getInt();
+ attrs.mtime = buffer.getInt();
+ }
+ } else if (version >= SFTP_V4) {
+ attrs.type = buffer.getUByte();
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ attrs.flags.add(Attribute.Size);
+ attrs.size = buffer.getLong();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ attrs.flags.add(Attribute.OwnerGroup);
+ attrs.owner = buffer.getString();
+ attrs.group = buffer.getString();
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ attrs.flags.add(Attribute.Perms);
+ attrs.perms = buffer.getInt();
+ }
+
+ // update the permissions according to the type
+ switch (attrs.type) {
+ case SSH_FILEXFER_TYPE_REGULAR:
+ attrs.perms |= S_IFREG;
+ break;
+ case SSH_FILEXFER_TYPE_DIRECTORY:
+ attrs.perms |= S_IFDIR;
+ break;
+ case SSH_FILEXFER_TYPE_SYMLINK:
+ attrs.perms |= S_IFLNK;
+ break;
+ default: // do nothing
+ }
+
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ attrs.flags.add(Attribute.AccessTime);
+ attrs.accessTime = readTime(buffer, flags);
+ attrs.atime = (int) attrs.accessTime.to(TimeUnit.SECONDS);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ attrs.flags.add(Attribute.CreateTime);
+ attrs.createTime = readTime(buffer, flags);
+ attrs.ctime = (int) attrs.createTime.to(TimeUnit.SECONDS);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ attrs.flags.add(Attribute.ModifyTime);
+ attrs.modifyTime = readTime(buffer, flags);
+ attrs.mtime = (int) attrs.modifyTime.to(TimeUnit.SECONDS);
+ }
+ // TODO: acl
+ } else {
+ throw new IllegalStateException("readAttributes - unsupported version: " + version);
+ }
+ return attrs;
+ }
+
+ protected FileTime readTime(Buffer buffer, int flags) {
+ long secs = buffer.getLong();
+ long millis = secs * 1000;
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ millis += buffer.getInt() / 1000000l;
+ }
+ return FileTime.from(millis, TimeUnit.MILLISECONDS);
+ }
+
+ protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException {
+ int version = getVersion();
+ if (version == SFTP_V3) {
+ int flags = 0;
+ for (Attribute a : attributes.flags) {
+ switch (a) {
+ case Size:
+ flags |= SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case UidGid:
+ flags |= SSH_FILEXFER_ATTR_UIDGID;
+ break;
+ case Perms:
+ flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AcModTime:
+ flags |= SSH_FILEXFER_ATTR_ACMODTIME;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flags);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.size);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_UIDGID) != 0) {
+ buffer.putInt(attributes.uid);
+ buffer.putInt(attributes.gid);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.perms);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACMODTIME) != 0) {
+ buffer.putInt(attributes.atime);
+ buffer.putInt(attributes.mtime);
+ }
+ } else if (version >= SFTP_V4) {
+ int flags = 0;
+ for (Attribute a : attributes.flags) {
+ switch (a) {
+ case Size:
+ flags |= SSH_FILEXFER_ATTR_SIZE;
+ break;
+ case OwnerGroup:
+ flags |= SSH_FILEXFER_ATTR_OWNERGROUP;
+ break;
+ case Perms:
+ flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
+ break;
+ case AccessTime:
+ flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
+ break;
+ case ModifyTime:
+ flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
+ break;
+ case CreateTime:
+ flags |= SSH_FILEXFER_ATTR_CREATETIME;
+ break;
+ default: // do nothing
+ }
+ }
+ buffer.putInt(flags);
+ buffer.putByte((byte) attributes.type);
+ if ((flags & SSH_FILEXFER_ATTR_SIZE) != 0) {
+ buffer.putLong(attributes.size);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_OWNERGROUP) != 0) {
+ buffer.putString(attributes.owner != null ? attributes.owner : "OWNER@", StandardCharsets.UTF_8);
+ buffer.putString(attributes.group != null ? attributes.group : "GROUP@", StandardCharsets.UTF_8);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_PERMISSIONS) != 0) {
+ buffer.putInt(attributes.perms);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_ACCESSTIME) != 0) {
+ buffer.putLong(attributes.accessTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.accessTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_CREATETIME) != 0) {
+ buffer.putLong(attributes.createTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.createTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ if ((flags & SSH_FILEXFER_ATTR_MODIFYTIME) != 0) {
+ buffer.putLong(attributes.modifyTime.to(TimeUnit.SECONDS));
+ if ((flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) != 0) {
+ long nanos = attributes.modifyTime.to(TimeUnit.NANOSECONDS);
+ nanos = nanos % TimeUnit.SECONDS.toNanos(1);
+ buffer.putInt((int) nanos);
+ }
+ buffer.putInt(attributes.atime);
+ }
+ // TODO: acl
+ } else {
+ throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version);
+ }
+ }
+
+ @Override
+ public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ int version = getVersion(), mode = 0;
+ if (version == SFTP_V3) {
+ for (OpenMode m : options) {
+ switch (m) {
+ case Read:
+ mode |= SSH_FXF_READ;
+ break;
+ case Write:
+ mode |= SSH_FXF_WRITE;
+ break;
+ case Append:
+ mode |= SSH_FXF_APPEND;
+ break;
+ case Create:
+ mode |= SSH_FXF_CREAT;
+ break;
+ case Truncate:
+ mode |= SSH_FXF_TRUNC;
+ break;
+ case Exclusive:
+ mode |= SSH_FXF_EXCL;
+ break;
+ default: // do nothing
+ }
+ }
+ } else {
+ if (version >= SFTP_V5) {
+ int access = 0;
+ if (options.contains(OpenMode.Read)) {
+ access |= ACE4_READ_DATA | ACE4_READ_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Write)) {
+ access |= ACE4_WRITE_DATA | ACE4_WRITE_ATTRIBUTES;
+ }
+ if (options.contains(OpenMode.Append)) {
+ access |= ACE4_APPEND_DATA;
+ }
+ buffer.putInt(access);
+ }
+
+ if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) {
+ mode |= SSH_FXF_CREATE_NEW;
+ } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) {
+ mode |= SSH_FXF_CREATE_TRUNCATE;
+ } else if (options.contains(OpenMode.Create)) {
+ mode |= SSH_FXF_OPEN_OR_CREATE;
+ } else if (options.contains(OpenMode.Truncate)) {
+ mode |= SSH_FXF_TRUNCATE_EXISTING;
+ } else {
+ mode |= SSH_FXF_OPEN_EXISTING;
+ }
+ }
+ buffer.putInt(mode);
+ writeAttributes(buffer, new Attributes());
+ return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPEN, buffer))));
+ }
+
+ @Override
+ public void close(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ checkStatus(receive(send(SSH_FXP_CLOSE, buffer)));
+ }
+
+ @Override
+ public void remove(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ checkStatus(receive(send(SSH_FXP_REMOVE, buffer)));
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(oldPath);
+ buffer.putString(newPath);
+
+ int numOptions = GenericUtils.size(options);
+ int version = getVersion();
+ if (version >= SFTP_V5) {
+ int opts = 0;
+ if (numOptions > 0) {
+ for (CopyMode opt : options) {
+ switch (opt) {
+ case Atomic:
+ opts |= SSH_FXP_RENAME_ATOMIC;
+ break;
+ case Overwrite:
+ opts |= SSH_FXP_RENAME_OVERWRITE;
+ break;
+ default: // do nothing
+ }
+ }
+ }
+ buffer.putInt(opts);
+ } else if (numOptions > 0) {
+ throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")"
+ + " - copy options can not be used with this SFTP version: " + options);
+ }
+ checkStatus(receive(send(SSH_FXP_RENAME, buffer)));
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ buffer.putLong(fileOffset);
+ buffer.putInt(len);
+ return checkData(receive(send(SSH_FXP_READ, buffer)), dstoff, dst);
+ }
+
+ protected int checkData(Buffer buffer, int dstoff, byte[] dst) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkData(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ if (substatus == SSH_FX_EOF) {
+ return -1;
+ }
+
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_DATA) {
+ int len = buffer.getInt();
+ buffer.getRawBytes(dst, dstoff, len);
+ return len;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException {
+ // do some bounds checking first
+ if ((fileOffset < 0) || (srcoff < 0) || (len < 0)) {
+ throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters "
+ + " are non-negative values: file-offset=" + fileOffset
+ + ", src-offset=" + srcoff + ", len=" + len);
+ }
+ if ((srcoff + len) > src.length) {
+ throw new IllegalArgumentException("write(" + handle + ")"
+ + " cannot read bytes " + srcoff + " to " + (srcoff + len)
+ + " when array is only of length " + src.length);
+ }
+
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + len + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ buffer.putLong(fileOffset);
+ buffer.putBytes(src, srcoff, len);
+ checkStatus(receive(send(SSH_FXP_WRITE, buffer)));
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path, StandardCharsets.UTF_8);
+ buffer.putInt(0);
+
+ int version = getVersion();
+ if (version != SFTP_V3) {
+ buffer.putByte((byte) 0);
+ }
+
+ checkStatus(receive(send(SSH_FXP_MKDIR, buffer)));
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ checkStatus(receive(send(SSH_FXP_RMDIR, buffer)));
+ }
+
+ @Override
+ public CloseableHandle openDir(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ return new DefaultCloseableHandle(this, checkHandle(receive(send(SSH_FXP_OPENDIR, buffer))));
+ }
+
+ @Override
+ public DirEntry[] readDir(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(handle.id.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(handle.id);
+ return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
+ }
+
+ protected DirEntry[] checkDir(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isTraceEnabled()) {
+ log.trace("checkDir(id={}) - status: {} [{}] {}", Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+ if (substatus == SSH_FX_EOF) {
+ return null;
+ }
+ throw new SftpException(substatus, msg);
+ } else if (type == SSH_FXP_NAME) {
+ int len = buffer.getInt();
+ DirEntry[] entries = new DirEntry[len];
+ for (int i = 0; i < len; i++) {
+ String name = buffer.getString();
+ int version = getVersion();
+ String longName = (version == SFTP_V3) ? buffer.getString() : null;
+ Attributes attrs = readAttributes(buffer);
+ if (log.isTraceEnabled()) {
+ log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
+ }
+
+ entries[i] = new DirEntry(name, longName, attrs);
+ }
+ return entries;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+
+ @Override
+ public String canonicalPath(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ return checkOneName(receive(send(SSH_FXP_REALPATH, buffer)));
+ }
+
+ @Override
+ public Attributes stat(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+
+ int version = getVersion();
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(receive(send(SSH_FXP_STAT, buffer)));
+ }
+
+ @Override
+ public Attributes lstat(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+
+ int version = getVersion();
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(receive(send(SSH_FXP_LSTAT, buffer)));
+ }
+
+ @Override
+ public Attributes stat(Handle handle) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+
+ int version = getVersion();
+ if (version >= SFTP_V4) {
+ buffer.putInt(SSH_FILEXFER_ATTR_ALL);
+ }
+
+ return checkAttributes(receive(send(SSH_FXP_FSTAT, buffer)));
+ }
+
+ @Override
+ public void setStat(String path, Attributes attributes) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(path);
+ writeAttributes(buffer, attributes);
+ checkStatus(receive(send(SSH_FXP_SETSTAT, buffer)));
+ }
+
+ @Override
+ public void setStat(Handle handle, Attributes attributes) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ writeAttributes(buffer, attributes);
+ checkStatus(receive(send(SSH_FXP_FSETSTAT, buffer)));
+ }
+
+ @Override
+ public String readLink(String path) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */);
+ buffer.putString(path);
+ return checkOneName(receive(send(SSH_FXP_READLINK, buffer)));
+ }
+
+ @Override
+ public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+ Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */);
+ int version = getVersion();
+ if (version < SFTP_V6) {
+ if (!symbolic) {
+ throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version);
+ }
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ checkStatus(receive(send(SSH_FXP_SYMLINK, buffer)));
+ } else {
+ buffer.putString(targetPath);
+ buffer.putString(linkPath);
+ buffer.putBoolean(symbolic);
+ checkStatus(receive(send(SSH_FXP_LINK, buffer)));
+ }
+ }
+
+ @Override
+ public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ buffer.putInt(mask);
+ checkStatus(receive(send(SSH_FXP_BLOCK, buffer)));
+ }
+
+ @Override
+ public void unlock(Handle handle, long offset, long length) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ buffer.putString(handle.id);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ checkStatus(receive(send(SSH_FXP_UNBLOCK, buffer)));
+ }
+
+ @Override
+ public Iterable<DirEntry> readDir(final String path) throws IOException {
+ return new Iterable<DirEntry>() {
+ @Override
+ public Iterator<DirEntry> iterator() {
+ return new Iterator<DirEntry>() {
+ private CloseableHandle handle;
+ private DirEntry[] entries;
+ private int index;
+
+ {
+ open();
+ load();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return (entries != null) && (index < entries.length);
+ }
+
+ @Override
+ public DirEntry next() {
+ DirEntry entry = entries[index++];
+ if (index >= entries.length) {
+ load();
+ }
+ return entry;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ private void open() {
+ try {
+ handle = openDir(path);
+ if (log.isDebugEnabled()) {
+ log.debug("readDir(" + path + ") handle=" + handle);
+ }
+ } catch (IOException e) {
+ if (log.isDebugEnabled()) {
+ log.debug("readDir(" + path + ") failed (" + e.getClass().getSimpleName() + ") to open dir: " + e.getMessage());
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("synthetic-access")
+ private void load() {
+ try {
+ entries = readDir(handle);
+ index = 0;
+ if (entries == null) {
+ handle.close();
+ }
+ } catch (IOException e) {
+ entries = null;
+ try {
+ handle.close();
+ } catch (IOException t) {
+ if (log.isTraceEnabled()) {
+ log.trace(t.getClass().getSimpleName() + " while close handle=" + handle
+ + " due to " + e.getClass().getSimpleName() + " [" + e.getMessage() + "]"
+ + ": " + t.getMessage());
+ }
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("readDir(" + path + ") Iterator#remove() N/A");
+ }
+ };
+ }
+ };
+ }
+
+ @Override
+ public InputStream read(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+ if (bufferSize < MIN_READ_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient read buffer size: " + bufferSize + ", min.=" + MIN_READ_BUFFER_SIZE);
+ }
+
+ return new InputStreamWithChannel() {
+ private byte[] bb = new byte[1];
+ private byte[] buffer = new byte[bufferSize];
+ private int index;
+ private int available;
+ private CloseableHandle handle = AbstractSftpClient.this.open(path, mode);
+ private long offset;
+
+ @Override
+ public boolean isOpen() {
+ return (handle != null) && handle.isOpen();
+ }
+
+ @Override
+ public int read() throws IOException {
+ int read = read(bb, 0, 1);
+ if (read > 0) {
+ return bb[0];
+ }
+
+ return read;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + path + ") stream closed");
+ }
+
+ int idx = off;
+ while (len > 0) {
+ if (index >= available) {
+ available = AbstractSftpClient.this.read(handle, offset, buffer, 0, buffer.length);
+ if (available < 0) {
+ if (idx == off) {
+ return -1;
+ } else {
+ break;
+ }
+ }
+ offset += available;
+ index = 0;
+ }
+ if (index >= available) {
+ break;
+ }
+ int nb = Math.min(len, available - index);
+ System.arraycopy(buffer, index, b, idx, nb);
+ index += nb;
+ idx += nb;
+ len -= nb;
+ }
+ return idx - off;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ try {
+ handle.close();
+ } finally {
+ handle = null;
+ }
+ }
+ }
+ };
+ }
+
+ @Override
+ public OutputStream write(final String path, final int bufferSize, final Collection<OpenMode> mode) throws IOException {
+ if (bufferSize < MIN_WRITE_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient write buffer size: " + bufferSize + ", min.=" + MIN_WRITE_BUFFER_SIZE);
+ }
+
+ return new OutputStreamWithChannel() {
+ private byte[] bb = new byte[1];
+ private byte[] buffer = new byte[bufferSize];
+ private int index;
+ private CloseableHandle handle = AbstractSftpClient.this.open(path, mode);
+ private long offset;
+
+ @Override
+ public boolean isOpen() {
+ return (handle != null) && handle.isOpen();
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ bb[0] = (byte) b;
+ write(bb, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + path + ")[len=" + len + "] stream is closed");
+ }
+
+ do {
+ int nb = Math.min(len, buffer.length - index);
+ System.arraycopy(b, off, buffer, index, nb);
+ index += nb;
+ if (index == buffer.length) {
+ flush();
+ }
+ off += nb;
+ len -= nb;
+ } while (len > 0);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("flush(" + path + ") stream is closed");
+ }
+
+ AbstractSftpClient.this.write(handle, offset, buffer, 0, index);
+ offset += index;
+ index = 0;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ try {
+ try {
+ if (index > 0) {
+ flush();
+ }
+ } finally {
+ handle.close();
+ }
+ } finally {
+ handle = null;
+ }
+ }
+ }
+ };
}
}