You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/16 11:48:00 UTC

[13/30] mina-sshd git commit: [SSHD-815] Extract SFTP in its own module

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpIterableDirEntry.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpIterableDirEntry.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpIterableDirEntry.java
new file mode 100644
index 0000000..945e0d7
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpIterableDirEntry.java
@@ -0,0 +1,72 @@
+/*
+ * 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.io.IOException;
+import java.util.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * Provides an {@link Iterable} implementation of the {@link DirEntry}-ies
+ * for a remote directory
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpIterableDirEntry implements Iterable<DirEntry> {
+    private final SftpClient client;
+    private final String path;
+
+    /**
+     * @param client The {@link SftpClient} instance to use for the iteration
+     * @param path The remote directory path
+     */
+    public SftpIterableDirEntry(SftpClient client, String path) {
+        this.client = Objects.requireNonNull(client, "No client instance");
+        this.path = ValidateUtils.checkNotNullAndNotEmpty(path, "No remote path");
+    }
+
+    /**
+     * The client instance
+     *
+     * @return {@link SftpClient} instance used to access the remote file
+     */
+    public final SftpClient getClient() {
+        return client;
+    }
+
+    /**
+     * The remotely accessed directory path
+     *
+     * @return Remote directory path
+     */
+    public final String getPath() {
+        return path;
+    }
+
+    @Override
+    public SftpDirEntryIterator iterator() {
+        try {
+            return new SftpDirEntryIterator(getClient(), getPath());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpOutputStreamWithChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpOutputStreamWithChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpOutputStreamWithChannel.java
new file mode 100644
index 0000000..cf6d972
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpOutputStreamWithChannel.java
@@ -0,0 +1,124 @@
+/*
+ * 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.io.IOException;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.OpenMode;
+import org.apache.sshd.common.util.io.OutputStreamWithChannel;
+
+/**
+ * Implements an output stream for a given remote file
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpOutputStreamWithChannel extends OutputStreamWithChannel {
+    private final SftpClient client;
+    private final String path;
+    private final byte[] bb =  new byte[1];
+    private final byte[] buffer;
+    private int index;
+    private CloseableHandle handle;
+    private long offset;
+
+    public SftpOutputStreamWithChannel(SftpClient client, int bufferSize, String path, Collection<OpenMode> mode) throws IOException {
+        this.client = Objects.requireNonNull(client, "No SFTP client instance");
+        this.path = path;
+        buffer = new byte[bufferSize];
+        handle = client.open(path, mode);
+    }
+
+    /**
+     * The client instance
+     *
+     * @return {@link SftpClient} instance used to access the remote file
+     */
+    public final SftpClient getClient() {
+        return client;
+    }
+
+    /**
+     * The remotely accessed file path
+     *
+     * @return Remote file path
+     */
+    public final String getPath() {
+        return path;
+    }
+
+    @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(" + getPath() + ")[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(" + getPath() + ") stream is closed");
+        }
+
+        client.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;
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
new file mode 100644
index 0000000..5567b58
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
@@ -0,0 +1,43 @@
+/*
+ * 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.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+
+import org.apache.sshd.common.file.util.BasePath;
+
+public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
+    public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
+        super(fileSystem, root, names);
+    }
+
+    @Override
+    public SftpPath toRealPath(LinkOption... options) throws IOException {
+        // TODO: handle links
+        SftpPath absolute = toAbsolutePath();
+        FileSystem fs = getFileSystem();
+        FileSystemProvider provider = fs.provider();
+        provider.checkAccess(absolute);
+        return absolute;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
new file mode 100644
index 0000000..49b4b48
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.nio.file.Path;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPathIterator implements Iterator<Path> {
+    private final SftpPath p;
+    private final Iterator<? extends SftpClient.DirEntry> it;
+    private boolean dotIgnored;
+    private boolean dotdotIgnored;
+    private SftpClient.DirEntry curEntry;
+
+    public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) {
+        this(path, (iter == null) ? null : iter.iterator());
+    }
+
+    public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) {
+        p = path;
+        it = iter;
+        curEntry = nextEntry();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return curEntry != null;
+    }
+
+    @Override
+    public Path next() {
+        if (curEntry == null) {
+            throw new NoSuchElementException("No next entry");
+        }
+
+        SftpClient.DirEntry entry = curEntry;
+        curEntry = nextEntry();
+        return p.resolve(entry.getFilename());
+    }
+
+    private SftpClient.DirEntry nextEntry() {
+        while ((it != null) && it.hasNext()) {
+            SftpClient.DirEntry entry = it.next();
+            String name = entry.getFilename();
+            if (".".equals(name) && (!dotIgnored)) {
+                dotIgnored = true;
+            } else if ("..".equals(name) && (!dotdotIgnored)) {
+                dotdotIgnored = true;
+            } else {
+                return entry;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
new file mode 100644
index 0000000..1fb614c
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
@@ -0,0 +1,94 @@
+/*
+ * 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.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributeView extends AbstractSftpFileAttributeView implements PosixFileAttributeView {
+    public SftpPosixFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+        super(provider, path, options);
+    }
+
+    @Override
+    public String name() {
+        return "posix";
+    }
+
+    @Override
+    public PosixFileAttributes readAttributes() throws IOException {
+        return new SftpPosixFileAttributes(path, readRemoteAttributes());
+    }
+
+    @Override
+    public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+        SftpClient.Attributes attrs = new SftpClient.Attributes();
+        if (lastModifiedTime != null) {
+            attrs.modifyTime(lastModifiedTime);
+        }
+        if (lastAccessTime != null) {
+            attrs.accessTime(lastAccessTime);
+        }
+        if (createTime != null) {
+            attrs.createTime(createTime);
+        }
+
+        if (GenericUtils.isEmpty(attrs.getFlags())) {
+            if (log.isDebugEnabled()) {
+                log.debug("setTimes({}) no changes", path);
+            }
+        } else {
+            writeRemoteAttributes(attrs);
+        }
+    }
+
+    @Override
+    public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+        provider.setAttribute(path, "permissions", perms, options);
+    }
+
+    @Override
+    public void setGroup(GroupPrincipal group) throws IOException {
+        provider.setAttribute(path, "group", group, options);
+    }
+
+    @Override
+    public UserPrincipal getOwner() throws IOException {
+        return readAttributes().owner();
+    }
+
+    @Override
+    public void setOwner(UserPrincipal owner) throws IOException {
+        provider.setAttribute(path, "owner", owner, options);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
new file mode 100644
index 0000000..a07e67f
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
@@ -0,0 +1,113 @@
+/*
+ * 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.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributes implements PosixFileAttributes {
+    private final Path path;
+    private final Attributes attributes;
+
+    public SftpPosixFileAttributes(Path path, Attributes attributes) {
+        this.path = path;
+        this.attributes = attributes;
+    }
+
+    /**
+     * @return The referenced attributes file {@link Path}
+     */
+    public final Path getPath() {
+        return path;
+    }
+
+    @Override
+    public UserPrincipal owner() {
+        String owner = attributes.getOwner();
+        return GenericUtils.isEmpty(owner) ? null : new SftpFileSystem.DefaultUserPrincipal(owner);
+    }
+
+    @Override
+    public GroupPrincipal group() {
+        String group = attributes.getGroup();
+        return GenericUtils.isEmpty(group) ? null : new SftpFileSystem.DefaultGroupPrincipal(group);
+    }
+
+    @Override
+    public Set<PosixFilePermission> permissions() {
+        return SftpFileSystemProvider.permissionsToAttributes(attributes.getPermissions());
+    }
+
+    @Override
+    public FileTime lastModifiedTime() {
+        return attributes.getModifyTime();
+    }
+
+    @Override
+    public FileTime lastAccessTime() {
+        return attributes.getAccessTime();
+    }
+
+    @Override
+    public FileTime creationTime() {
+        return attributes.getCreateTime();
+    }
+
+    @Override
+    public boolean isRegularFile() {
+        return attributes.isRegularFile();
+    }
+
+    @Override
+    public boolean isDirectory() {
+        return attributes.isDirectory();
+    }
+
+    @Override
+    public boolean isSymbolicLink() {
+        return attributes.isSymbolicLink();
+    }
+
+    @Override
+    public boolean isOther() {
+        return attributes.isOther();
+    }
+
+    @Override
+    public long size() {
+        return attributes.getSize();
+    }
+
+    @Override
+    public Object fileKey() {
+        // TODO consider implementing this
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpRemotePathChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpRemotePathChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpRemotePathChannel.java
new file mode 100644
index 0000000..9195009
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpRemotePathChannel.java
@@ -0,0 +1,412 @@
+/*
+ * 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.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpRemotePathChannel extends FileChannel {
+    public static final String COPY_BUFSIZE_PROP = "sftp-channel-copy-buf-size";
+    public static final int DEFAULT_TRANSFER_BUFFER_SIZE = IoUtils.DEFAULT_COPY_SIZE;
+
+    public static final Set<SftpClient.OpenMode> READ_MODES =
+            Collections.unmodifiableSet(EnumSet.of(SftpClient.OpenMode.Read));
+
+    public static final Set<SftpClient.OpenMode> WRITE_MODES =
+            Collections.unmodifiableSet(
+                    EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Append, SftpClient.OpenMode.Create, SftpClient.OpenMode.Truncate));
+
+    private final String path;
+    private final Collection<SftpClient.OpenMode> modes;
+    private final boolean closeOnExit;
+    private final SftpClient sftp;
+    private final SftpClient.CloseableHandle handle;
+    private final Object lock = new Object();
+    private final AtomicLong posTracker = new AtomicLong(0L);
+    private final AtomicReference<Thread> blockingThreadHolder = new AtomicReference<>(null);
+
+    public SftpRemotePathChannel(String path, SftpClient sftp, boolean closeOnExit, Collection<SftpClient.OpenMode> modes) throws IOException {
+        this.path = ValidateUtils.checkNotNullAndNotEmpty(path, "No remote file path specified");
+        this.modes = Objects.requireNonNull(modes, "No channel modes specified");
+        this.sftp = Objects.requireNonNull(sftp, "No SFTP client instance");
+        this.closeOnExit = closeOnExit;
+        this.handle = sftp.open(path, modes);
+    }
+
+    public String getRemotePath() {
+        return path;
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        return (int) doRead(Collections.singletonList(dst), -1);
+    }
+
+    @Override
+    public int read(ByteBuffer dst, long position) throws IOException {
+        if (position < 0) {
+            throw new IllegalArgumentException("read(" + getRemotePath() + ") illegal position to read from: " + position);
+        }
+        return (int) doRead(Collections.singletonList(dst), position);
+    }
+
+    @Override
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+        List<ByteBuffer> buffers = Arrays.asList(dsts).subList(offset, offset + length);
+        return doRead(buffers, -1);
+    }
+
+    protected long doRead(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen(READ_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            boolean eof = false;
+            long curPos = (position >= 0L) ? position : posTracker.get();
+            try {
+                long totalRead = 0;
+                beginBlocking();
+                loop:
+                for (ByteBuffer buffer : buffers) {
+                    while (buffer.remaining() > 0) {
+                        ByteBuffer wrap = buffer;
+                        if (!buffer.hasArray()) {
+                            wrap = ByteBuffer.allocate(Math.min(IoUtils.DEFAULT_COPY_SIZE, buffer.remaining()));
+                        }
+                        int read = sftp.read(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), wrap.remaining());
+                        if (read > 0) {
+                            if (wrap == buffer) {
+                                wrap.position(wrap.position() + read);
+                            } else {
+                                buffer.put(wrap.array(), wrap.arrayOffset(), read);
+                            }
+                            curPos += read;
+                            totalRead += read;
+                        } else {
+                            eof = read == -1;
+                            break loop;
+                        }
+                    }
+                }
+                completed = true;
+                if (totalRead > 0) {
+                    return totalRead;
+                }
+
+                if (eof) {
+                    return -1;
+                } else {
+                    return 0;
+                }
+            } finally {
+                if (position < 0L) {
+                    posTracker.set(curPos);
+                }
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        return (int) doWrite(Collections.singletonList(src), -1);
+    }
+
+    @Override
+    public int write(ByteBuffer src, long position) throws IOException {
+        if (position < 0L) {
+            throw new IllegalArgumentException("write(" + getRemotePath() + ") illegal position to write to: " + position);
+        }
+        return (int) doWrite(Collections.singletonList(src), position);
+    }
+
+    @Override
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+        List<ByteBuffer> buffers = Arrays.asList(srcs).subList(offset, offset + length);
+        return doWrite(buffers, -1);
+    }
+
+    protected long doWrite(List<ByteBuffer> buffers, long position) throws IOException {
+        ensureOpen(WRITE_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            long curPos = (position >= 0L) ? position : posTracker.get();
+            try {
+                long totalWritten = 0L;
+                beginBlocking();
+                for (ByteBuffer buffer : buffers) {
+                    while (buffer.remaining() > 0) {
+                        ByteBuffer wrap = buffer;
+                        if (!buffer.hasArray()) {
+                            wrap = ByteBuffer.allocate(Math.min(IoUtils.DEFAULT_COPY_SIZE, buffer.remaining()));
+                            buffer.get(wrap.array(), wrap.arrayOffset(), wrap.remaining());
+                        }
+                        int written = wrap.remaining();
+                        sftp.write(handle, curPos, wrap.array(), wrap.arrayOffset() + wrap.position(), written);
+                        if (wrap == buffer) {
+                            wrap.position(wrap.position() + written);
+                        }
+                        curPos += written;
+                        totalWritten += written;
+                    }
+                }
+                completed = true;
+                return totalWritten;
+            } finally {
+                if (position < 0L) {
+                    posTracker.set(curPos);
+                }
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public long position() throws IOException {
+        ensureOpen(Collections.emptySet());
+        return posTracker.get();
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+        if (newPosition < 0L) {
+            throw new IllegalArgumentException("position(" + getRemotePath() + ") illegal file channel position: " + newPosition);
+        }
+
+        ensureOpen(Collections.emptySet());
+        posTracker.set(newPosition);
+        return this;
+    }
+
+    @Override
+    public long size() throws IOException {
+        ensureOpen(Collections.emptySet());
+        return sftp.stat(handle).getSize();
+    }
+
+    @Override
+    public FileChannel truncate(long size) throws IOException {
+        ensureOpen(Collections.emptySet());
+        sftp.setStat(handle, new SftpClient.Attributes().size(size));
+        return this;
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+        ensureOpen(Collections.emptySet());
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
+        if ((position < 0) || (count < 0)) {
+            throw new IllegalArgumentException("transferTo(" + getRemotePath() + ") illegal position (" + position + ") or count (" + count + ")");
+        }
+        ensureOpen(READ_MODES);
+        synchronized (lock) {
+            boolean completed = false;
+            boolean eof = false;
+            long curPos = position;
+            try {
+                beginBlocking();
+
+                int bufSize = (int) Math.min(count, Short.MAX_VALUE + 1);
+                byte[] buffer = new byte[bufSize];
+                long totalRead = 0L;
+                while (totalRead < count) {
+                    int read = sftp.read(handle, curPos, buffer, 0, buffer.length);
+                    if (read > 0) {
+                        ByteBuffer wrap = ByteBuffer.wrap(buffer);
+                        while (wrap.remaining() > 0) {
+                            target.write(wrap);
+                        }
+                        curPos += read;
+                        totalRead += read;
+                    } else {
+                        eof = read == -1;
+                    }
+                }
+                completed = true;
+                return totalRead > 0 ? totalRead : eof ? -1 : 0;
+            } finally {
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+        if ((position < 0) || (count < 0)) {
+            throw new IllegalArgumentException("transferFrom(" + getRemotePath() + ") illegal position (" + position + ") or count (" + count + ")");
+        }
+        ensureOpen(WRITE_MODES);
+
+        int copySize = sftp.getClientSession().getIntProperty(COPY_BUFSIZE_PROP, DEFAULT_TRANSFER_BUFFER_SIZE);
+        boolean completed = false;
+        long curPos = (position >= 0L) ? position : posTracker.get();
+        long totalRead = 0L;
+        byte[] buffer = new byte[(int) Math.min(copySize, count)];
+
+        synchronized (lock) {
+            try {
+                beginBlocking();
+
+                while (totalRead < count) {
+                    ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, (int) Math.min(buffer.length, count - totalRead));
+                    int read = src.read(wrap);
+                    if (read > 0) {
+                        sftp.write(handle, curPos, buffer, 0, read);
+                        curPos += read;
+                        totalRead += read;
+                    } else {
+                        break;
+                    }
+                }
+                completed = true;
+                return totalRead;
+            } finally {
+                endBlocking(completed);
+            }
+        }
+    }
+
+    @Override
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+        throw new UnsupportedOperationException("map(" + getRemotePath() + ")[" + mode + "," + position + "," + size + "] N/A");
+    }
+
+    @Override
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+        return tryLock(position, size, shared);
+    }
+
+    @Override
+    public FileLock tryLock(final long position, final long size, boolean shared) throws IOException {
+        ensureOpen(Collections.emptySet());
+
+        try {
+            sftp.lock(handle, position, size, 0);
+        } catch (SftpException e) {
+            if (e.getStatus() == SftpConstants.SSH_FX_LOCK_CONFLICT) {
+                throw new OverlappingFileLockException();
+            }
+            throw e;
+        }
+
+        return new FileLock(this, position, size, shared) {
+            private final AtomicBoolean valid = new AtomicBoolean(true);
+
+            @Override
+            public boolean isValid() {
+                return acquiredBy().isOpen() && valid.get();
+            }
+
+            @SuppressWarnings("synthetic-access")
+            @Override
+            public void release() throws IOException {
+                if (valid.compareAndSet(true, false)) {
+                    sftp.unlock(handle, position, size);
+                }
+            }
+        };
+    }
+
+    @Override
+    protected void implCloseChannel() throws IOException {
+        try {
+            final Thread thread = blockingThreadHolder.get();
+            if (thread != null) {
+                thread.interrupt();
+            }
+        } finally {
+            try {
+                handle.close();
+            } finally {
+                if (closeOnExit) {
+                    sftp.close();
+                }
+            }
+        }
+    }
+
+    private void beginBlocking() {
+        begin();
+        blockingThreadHolder.set(Thread.currentThread());
+    }
+
+    private void endBlocking(boolean completed) throws AsynchronousCloseException {
+        blockingThreadHolder.set(null);
+        end(completed);
+    }
+
+    /**
+     * Checks that the channel is open and that its current mode contains
+     * at least one of the required ones
+     *
+     * @param reqModes The required modes - ignored if {@code null}/empty
+     * @throws IOException If channel not open or the required modes are not
+     *                     satisfied
+     */
+    private void ensureOpen(Collection<SftpClient.OpenMode> reqModes) throws IOException {
+        if (!isOpen()) {
+            throw new ClosedChannelException();
+        }
+
+        if (GenericUtils.size(reqModes) > 0) {
+            for (SftpClient.OpenMode m : reqModes) {
+                if (this.modes.contains(m)) {
+                    return;
+                }
+            }
+
+            throw new IOException("ensureOpen(" + getRemotePath() + ") current channel modes (" + this.modes + ") do contain any of the required: " + reqModes);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getRemotePath();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
new file mode 100644
index 0000000..3c58da3
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpVersionSelector.java
@@ -0,0 +1,126 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.stream.StreamSupport;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface SftpVersionSelector {
+    /**
+     * An {@link SftpVersionSelector} that returns the current version
+     */
+    SftpVersionSelector CURRENT = new NamedVersionSelector("CURRENT", (session, current, available) -> current);
+
+    /**
+     * An {@link SftpVersionSelector} that returns the maximum available version
+     */
+    SftpVersionSelector MAXIMUM = new NamedVersionSelector("MAXIMUM", (session, current, available) ->
+            GenericUtils.stream(available).mapToInt(Integer::intValue).max().orElse(current));
+
+    /**
+     * An {@link SftpVersionSelector} that returns the maximum available version
+     */
+    SftpVersionSelector MINIMUM = new NamedVersionSelector("MINIMUM", (session, current, available) ->
+            GenericUtils.stream(available).mapToInt(Integer::intValue).min().orElse(current));
+
+    /**
+     * @param session   The {@link ClientSession} through which the SFTP connection is made
+     * @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(ClientSession session, int current, List<Integer> available);
+
+    /**
+     * Creates a selector the always returns the requested (fixed version) regardless
+     * of what the current or reported available versions are. If the requested version
+     * is not reported as available then an exception will be eventually thrown by the
+     * client during re-negotiation phase.
+     *
+     * @param version The requested version
+     * @return The {@link SftpVersionSelector}
+     */
+    static SftpVersionSelector fixedVersionSelector(int version) {
+        return new NamedVersionSelector(Integer.toString(version), (session, current, available) -> version);
+    }
+
+    /**
+     * Selects a version in order of preference - if none of the preferred
+     * versions is listed as available then an exception is thrown when the
+     * {@link SftpVersionSelector#selectVersion(ClientSession, int, List)} method is invoked
+     *
+     * @param preferred The preferred versions in decreasing order of
+     * preference (i.e., most preferred is 1st) - may not be {@code null}/empty
+     * @return A {@link SftpVersionSelector} that attempts to select
+     * the most preferred version that is also listed as available.
+     */
+    static SftpVersionSelector preferredVersionSelector(int... preferred) {
+        return preferredVersionSelector(NumberUtils.asList(preferred));
+    }
+
+    /**
+     * Selects a version in order of preference - if none of the preferred
+     * versions is listed as available then an exception is thrown when the
+     * {@link SftpVersionSelector#selectVersion(ClientSession, int, List)} method is invoked
+     *
+     * @param preferred The preferred versions in decreasing order of
+     * preference (i.e., most preferred is 1st)
+     * @return A {@link SftpVersionSelector} that attempts to select
+     * the most preferred version that is also listed as available.
+     */
+    static SftpVersionSelector preferredVersionSelector(Iterable<? extends Number> preferred) {
+        ValidateUtils.checkNotNullAndNotEmpty((Collection<?>) preferred, "Empty preferred versions");
+        return new NamedVersionSelector(GenericUtils.join(preferred, ','), (session, current, available) -> StreamSupport.stream(preferred.spliterator(), false)
+            .mapToInt(Number::intValue)
+            .filter(v -> v == current || available.contains(v))
+            .findFirst()
+            .orElseThrow(() -> new IllegalStateException("Preferred versions (" + preferred + ") not available: " + available)));
+    }
+
+    class NamedVersionSelector implements SftpVersionSelector {
+        private final String name;
+        private final SftpVersionSelector selector;
+
+        public NamedVersionSelector(String name, SftpVersionSelector selector) {
+            this.name = name;
+            this.selector = selector;
+        }
+
+        @Override
+        public int selectVersion(ClientSession session, int current, List<Integer> available) {
+            return selector.selectVersion(session, current, available);
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/StfpIterableDirHandle.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/StfpIterableDirHandle.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/StfpIterableDirHandle.java
new file mode 100644
index 0000000..c3be157
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/StfpIterableDirHandle.java
@@ -0,0 +1,59 @@
+/*
+ * 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.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+
+public class StfpIterableDirHandle implements Iterable<DirEntry> {
+    private final SftpClient client;
+    private final Handle handle;
+
+    /**
+     * @param client The {@link SftpClient} to use for iteration
+     * @param handle The remote directory {@link Handle}
+     */
+    public StfpIterableDirHandle(SftpClient client, Handle handle) {
+        this.client = Objects.requireNonNull(client, "No client instance");
+        this.handle = handle;
+    }
+
+    /**
+     * The client instance
+     *
+     * @return {@link SftpClient} instance used to access the remote file
+     */
+    public final SftpClient getClient() {
+        return client;
+    }
+
+    /**
+     * @return The remote directory {@link Handle}
+     */
+    public final Handle getHandle() {
+        return handle;
+    }
+
+    @Override
+    public SftpDirEntryIterator iterator() {
+        return new SftpDirEntryIterator(getClient(), getHandle());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
new file mode 100644
index 0000000..9e83837
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
@@ -0,0 +1,162 @@
+/*
+ * 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.extensions;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.CheckFileHandleExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.CheckFileNameExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.CopyDataExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.CopyFileExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.MD5FileExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.MD5HandleExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.helpers.SpaceAvailableExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatHandleExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.helpers.OpenSSHFsyncExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.helpers.OpenSSHStatHandleExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.helpers.OpenSSHStatPathExtensionImpl;
+import org.apache.sshd.common.NamedResource;
+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.openssh.FstatVfsExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FsyncExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.StatVfsExtensionParser;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinSftpClientExtensions implements SftpClientExtensionFactory {
+    COPY_FILE(SftpConstants.EXT_COPY_FILE, CopyFileExtension.class) {
+        @Override   // co-variant return
+        public CopyFileExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new CopyFileExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    COPY_DATA(SftpConstants.EXT_COPY_DATA, CopyDataExtension.class) {
+        @Override   // co-variant return
+        public CopyDataExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new CopyDataExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    MD5_FILE(SftpConstants.EXT_MD5_HASH, MD5FileExtension.class) {
+        @Override   // co-variant return
+        public MD5FileExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new MD5FileExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    MD5_HANDLE(SftpConstants.EXT_MD5_HASH_HANDLE, MD5HandleExtension.class) {
+        @Override   // co-variant return
+        public MD5HandleExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new MD5HandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    CHECK_FILE_NAME(SftpConstants.EXT_CHECK_FILE_NAME, CheckFileNameExtension.class) {
+        @Override   // co-variant return
+        public CheckFileNameExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new CheckFileNameExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    CHECK_FILE_HANDLE(SftpConstants.EXT_CHECK_FILE_HANDLE, CheckFileHandleExtension.class) {
+        @Override   // co-variant return
+        public CheckFileHandleExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new CheckFileHandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    SPACE_AVAILABLE(SftpConstants.EXT_SPACE_AVAILABLE, SpaceAvailableExtension.class) {
+        @Override   // co-variant return
+        public SpaceAvailableExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new SpaceAvailableExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+        }
+    },
+    OPENSSH_FSYNC(FsyncExtensionParser.NAME, OpenSSHFsyncExtension.class) {
+        @Override   // co-variant return
+        public OpenSSHFsyncExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new OpenSSHFsyncExtensionImpl(client, raw, extensions);
+        }
+    },
+    OPENSSH_STAT_HANDLE(FstatVfsExtensionParser.NAME, OpenSSHStatHandleExtension.class) {
+        @Override   // co-variant return
+        public OpenSSHStatHandleExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new OpenSSHStatHandleExtensionImpl(client, raw, extensions);
+        }
+    },
+    OPENSSH_STAT_PATH(StatVfsExtensionParser.NAME, OpenSSHStatPathExtension.class) {
+        @Override   // co-variant return
+        public OpenSSHStatPathExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed) {
+            return new OpenSSHStatPathExtensionImpl(client, raw, extensions);
+        }
+    };
+
+    public static final Set<BuiltinSftpClientExtensions> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(BuiltinSftpClientExtensions.class));
+
+    private final String name;
+
+    private final Class<? extends SftpClientExtension> type;
+
+    BuiltinSftpClientExtensions(String name, Class<? extends SftpClientExtension> type) {
+        this.name = name;
+        this.type = type;
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final Class<? extends SftpClientExtension> getType() {
+        return type;
+    }
+
+    public static BuiltinSftpClientExtensions fromName(String n) {
+        return NamedResource.findByName(n, String.CASE_INSENSITIVE_ORDER, VALUES);
+    }
+
+    public static BuiltinSftpClientExtensions fromInstance(Object o) {
+        return fromType((o == null) ? null : o.getClass());
+    }
+
+    public static BuiltinSftpClientExtensions fromType(Class<?> type) {
+        if ((type == null) || (!SftpClientExtension.class.isAssignableFrom(type))) {
+            return null;
+        }
+
+        // the base class is assignable to everybody so we cannot distinguish between the enum(s)
+        if (SftpClientExtension.class == type) {
+            return null;
+        }
+
+        for (BuiltinSftpClientExtensions v : VALUES) {
+            Class<?> vt = v.getType();
+            if (vt.isAssignableFrom(type)) {
+                return v;
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java
new file mode 100644
index 0000000..3261a63
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.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.extensions;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.2</A>
+ */
+public interface CheckFileHandleExtension extends SftpClientExtension {
+    /**
+     * @param handle      Remote file {@link Handle} - must be a file and opened for read
+     * @param algorithms  Hash algorithms in preferred order
+     * @param startOffset Start offset of the hash
+     * @param length      Length of data to hash - if zero then till EOF
+     * @param blockSize   Input block size to calculate individual hashes - if
+     *                    zero the <U>one</U> hash of <U>all</U> the data
+     * @return An <U>immutable</U> {@link java.util.Map.Entry} where key=hash algorithm name,
+     * value=the calculated hashes.
+     * @throws IOException If failed to execute the command
+     */
+    Map.Entry<String, Collection<byte[]>> checkFileHandle(Handle handle, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java
new file mode 100644
index 0000000..14e0204
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java
@@ -0,0 +1,43 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.2</A>
+ */
+public interface CheckFileNameExtension extends SftpClientExtension {
+    /**
+     * @param name        Remote file name/path
+     * @param algorithms  Hash algorithms in preferred order
+     * @param startOffset Start offset of the hash
+     * @param length      Length of data to hash - if zero then till EOF
+     * @param blockSize   Input block size to calculate individual hashes - if
+     *                    zero the <U>one</U> hash of <U>all</U> the data
+     * @return An <U>immutable</U> {@link java.util.Map.Entry} key left=hash algorithm name,
+     * value=the calculated hashes.
+     * @throws IOException If failed to execute the command
+     */
+    Map.Entry<String, Collection<byte[]>> checkFileName(String name, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyDataExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyDataExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyDataExtension.java
new file mode 100644
index 0000000..0250b86
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyDataExtension.java
@@ -0,0 +1,34 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+
+/**
+ * Implements the &quot;copy-data&quot; extension
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/id/draft-ietf-secsh-filexfer-extensions-00.txt">DRAFT 00 section 7</A>
+ */
+public interface CopyDataExtension extends SftpClientExtension {
+    void copyData(Handle readHandle, long readOffset, long readLength, Handle writeHandle, long writeOffset) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
new file mode 100644
index 0000000..749c1a6
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
@@ -0,0 +1,36 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6">copy-file extension</A>
+ */
+public interface CopyFileExtension extends SftpClientExtension {
+    /**
+     * @param src                  The (<U>remote</U>) file source path
+     * @param dst                  The (<U>remote</U>) file destination path
+     * @param overwriteDestination If {@code true} then OK to override destination if exists
+     * @throws IOException If failed to execute the command or extension not supported
+     */
+    void copyFile(String src, String dst, boolean overwriteDestination) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
new file mode 100644
index 0000000..2e8d23f
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
@@ -0,0 +1,40 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.1</A>
+ */
+public interface MD5FileExtension extends SftpClientExtension {
+    /**
+     * @param path      The (remote) path
+     * @param offset    The offset to start calculating the hash
+     * @param length    The number of data bytes to calculate the hash on - if
+     *                  greater than available, then up to whatever is available
+     * @param quickHash A quick-hash of the 1st 2048 bytes - ignored if {@code null}/empty
+     * @return The hash value if the quick hash matches (or {@code null}/empty), or
+     * {@code null}/empty if the quick hash is provided and it does not match
+     * @throws IOException If failed to calculate the hash
+     */
+    byte[] getHash(String path, long offset, long length, byte[] quickHash) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
new file mode 100644
index 0000000..18392fa
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
@@ -0,0 +1,43 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.1</A>
+ */
+public interface MD5HandleExtension extends SftpClientExtension {
+    /**
+     * @param handle    The (remote) file {@code Handle}
+     * @param offset    The offset to start calculating the hash
+     * @param length    The number of data bytes to calculate the hash on - if
+     *                  greater than available, then up to whatever is available
+     * @param quickHash A quick-hash of the 1st 2048 bytes - ignored if {@code null}/empty
+     * @return The hash value if the quick hash matches (or {@code null}/empty), or
+     * {@code null}/empty if the quick hash is provided and it does not match
+     * @throws IOException If failed to calculate the hash
+     */
+    byte[] getHash(SftpClient.Handle handle, long offset, long length, byte[] quickHash) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
new file mode 100644
index 0000000..c27a9e1
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
@@ -0,0 +1,34 @@
+/*
+ * 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.extensions;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.OptionalFeature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientExtension extends NamedResource, OptionalFeature {
+    /**
+     * @return The {@link SftpClient} used to issue the extended command
+     */
+    SftpClient getClient();
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
new file mode 100644
index 0000000..0692a04
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.extensions;
+
+import java.util.Map;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientExtensionFactory extends NamedResource {
+    default SftpClientExtension create(SftpClient client, RawSftpClient raw) {
+        Map<String, byte[]> extensions = client.getServerExtensions();
+        return create(client, raw, extensions, ParserUtils.parse(extensions));
+    }
+
+    SftpClientExtension create(SftpClient client, RawSftpClient raw, Map<String, byte[]> extensions, Map<String, ?> parsed);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SpaceAvailableExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SpaceAvailableExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SpaceAvailableExtension.java
new file mode 100644
index 0000000..2cc938b
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SpaceAvailableExtension.java
@@ -0,0 +1,34 @@
+/*
+ * 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.extensions;
+
+import java.io.IOException;
+
+import org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo;
+
+/**
+ * Implements the &quot;space-available&quot; extension
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 section 9.2</A>
+ */
+public interface SpaceAvailableExtension extends SftpClientExtension {
+    SpaceAvailableExtensionInfo available(String path) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtension.java
new file mode 100644
index 0000000..1411098
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtension.java
@@ -0,0 +1,76 @@
+/*
+ * 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.extensions.helpers;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractCheckFileExtension extends AbstractSftpClientExtension {
+    protected AbstractCheckFileExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
+        super(name, client, raw, extras);
+    }
+
+    protected SimpleImmutableEntry<String, Collection<byte[]>> doGetHash(Object target, Collection<String> algorithms, long offset, long length, int blockSize) throws IOException {
+        Buffer buffer = getCommandBuffer(target, Byte.MAX_VALUE);
+        putTarget(buffer, target);
+        buffer.putString(GenericUtils.join(algorithms, ','));
+        buffer.putLong(offset);
+        buffer.putLong(length);
+        buffer.putInt(blockSize);
+
+        if (log.isDebugEnabled()) {
+            log.debug("doGetHash({})[{}] - offset={}, length={}, block-size={}",
+                      getName(), (target instanceof CharSequence) ? target : BufferUtils.toHex(BufferUtils.EMPTY_HEX_SEPARATOR, (byte[]) target),
+                      offset, length, blockSize);
+        }
+
+        buffer = checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer)));
+        if (buffer == null) {
+            throw new StreamCorruptedException("Missing extended reply data");
+        }
+
+        String targetType = buffer.getString();
+        if (String.CASE_INSENSITIVE_ORDER.compare(targetType, SftpConstants.EXT_CHECK_FILE) != 0) {
+            throw new StreamCorruptedException("Mismatched reply type: expected=" + SftpConstants.EXT_CHECK_FILE + ", actual=" + targetType);
+        }
+
+        String algo = buffer.getString();
+        Collection<byte[]> hashes = new LinkedList<>();
+        while (buffer.available() > 0) {
+            byte[] hashValue = buffer.getBytes();
+            hashes.add(hashValue);
+        }
+
+        return new SimpleImmutableEntry<>(algo, hashes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtension.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtension.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtension.java
new file mode 100644
index 0000000..ab00f9e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtension.java
@@ -0,0 +1,75 @@
+/*
+ * 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.extensions.helpers;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractMD5HashExtension extends AbstractSftpClientExtension {
+    protected AbstractMD5HashExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
+        super(name, client, raw, extras);
+    }
+
+    protected byte[] doGetHash(Object target, long offset, long length, byte[] quickHash) throws IOException {
+        Buffer buffer = getCommandBuffer(target, Long.SIZE + 2 * Long.BYTES + Integer.BYTES + NumberUtils.length(quickHash));
+        String opcode = getName();
+        putTarget(buffer, target);
+        buffer.putLong(offset);
+        buffer.putLong(length);
+        buffer.putBytes((quickHash == null) ? GenericUtils.EMPTY_BYTE_ARRAY : quickHash);
+
+        boolean debugEnabled = log.isDebugEnabled();
+        if (debugEnabled) {
+            log.debug("doGetHash({})[{}] - offset={}, length={}, quick-hash={}",
+                      opcode, (target instanceof CharSequence) ? target : BufferUtils.toHex(BufferUtils.EMPTY_HEX_SEPARATOR, (byte[]) target),
+                      offset, length, BufferUtils.toHex(':', quickHash));
+        }
+
+        buffer = checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer)));
+        if (buffer == null) {
+            throw new StreamCorruptedException("Missing extended reply data");
+        }
+
+        String targetType = buffer.getString();
+        if (String.CASE_INSENSITIVE_ORDER.compare(targetType, opcode) != 0) {
+            throw new StreamCorruptedException("Mismatched reply target type: expected=" + opcode + ", actual=" + targetType);
+        }
+
+        byte[] hashValue = buffer.getBytes();
+        if (debugEnabled) {
+            log.debug("doGetHash({})[{}] - offset={}, length={}, quick-hash={} - result={}",
+                      opcode, target, offset, length,
+                      BufferUtils.toHex(':', quickHash), BufferUtils.toHex(':', hashValue));
+        }
+
+        return hashValue;
+    }
+}