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/06 13:46:54 UTC
mina-sshd git commit: [SSHD-527] Generate a synthetic ".." folder in
SFTP listing of folder contents
Repository: mina-sshd
Updated Branches:
refs/heads/master 7d79f7029 -> f7f21bc85
[SSHD-527] Generate a synthetic ".." folder in SFTP listing of folder contents
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f7f21bc8
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f7f21bc8
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f7f21bc8
Branch: refs/heads/master
Commit: f7f21bc857aa021b787b581a13489855105e6e66
Parents: 7d79f70
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jul 6 14:46:40 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jul 6 14:46:40 2015 +0300
----------------------------------------------------------------------
.../java/org/apache/sshd/client/SshClient.java | 386 ++++++++++++-------
.../subsystem/sftp/AbstractSftpClient.java | 18 +-
.../sshd/client/subsystem/sftp/SftpClient.java | 20 +-
.../client/subsystem/sftp/SftpFileSystem.java | 2 +-
.../subsystem/sftp/SftpFileSystemProvider.java | 29 +-
.../server/subsystem/sftp/SftpSubsystem.java | 53 ++-
.../org/apache/sshd/client/SshClientMain.java | 31 ++
.../subsystem/sftp/SftpFileSystemTest.java | 3 +
.../sshd/client/subsystem/sftp/SftpTest.java | 29 +-
9 files changed, 399 insertions(+), 172 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index fca2412..ae0b758 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -19,8 +19,10 @@
package org.apache.sshd.client;
import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
@@ -65,6 +67,7 @@ import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoConnectFuture;
import org.apache.sshd.common.io.IoConnector;
+import org.apache.sshd.common.keyprovider.AbstractFileKeyPairProvider;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.SecurityUtils;
@@ -317,6 +320,170 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
Main class implementation
*=================================*/
+ // NOTE: ClientSession#getFactoryManager is the SshClient
+ public static ClientSession setupClientSession(
+ String portOption, final BufferedReader stdin, final PrintStream stdout, final PrintStream stderr, String ... args)
+ throws Exception {
+
+ int port = -1;
+ String host = null;
+ String login = null;
+ boolean error = false;
+ List<File> identities = new ArrayList<File>();
+ Map<String, String> options = new LinkedHashMap<String, String>();
+ int numArgs = GenericUtils.length(args);
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ if (portOption.equals(argName)) {
+ if (i + 1 >= numArgs) {
+ stderr.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+
+ if (port > 0) {
+ stderr.println(argName + " option value re-specified: " + port);
+ error = true;
+ break;
+ }
+
+ if ((port=Integer.parseInt(args[++i])) <= 0) {
+ stderr.println("Bad option value for " + argName + ": " + port);
+ error = true;
+ break;
+ }
+ } else if ("-i".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ stderr.println("option requires and argument: " + argName);
+ error = true;
+ break;
+ }
+
+ File f = new File(args[++i]);
+ identities.add(f);
+ } else if ("-o".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ stderr.println("option requires and argument: " + argName);
+ error = true;
+ break;
+ }
+ String opt = args[++i];
+ int idx = opt.indexOf('=');
+ if (idx <= 0) {
+ stderr.println("bad syntax for option: " + opt);
+ error = true;
+ break;
+ }
+ options.put(opt.substring(0, idx), opt.substring(idx + 1));
+ } else if ("-l".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ stderr.println("option requires an argument: " + argName);
+ error = true;
+ break;
+ }
+
+ if (login != null) {
+ stderr.println(argName + " option value re-specified: " + port);
+ error = true;
+ break;
+ }
+
+ login = args[++i];
+ } else if (argName.charAt(0) != '-') {
+ host = argName;
+ if (login == null) {
+ int pos = host.indexOf('@'); // check if user@host
+ if (pos > 0) {
+ login = host.substring(0, pos);
+ host = host.substring(pos + 1);
+ }
+ }
+ }
+ }
+
+ if ((!error) && GenericUtils.isEmpty(host)) {
+ stderr.println("Hostname not specified");
+ error = true;
+ }
+
+ if (login == null) {
+ login = System.getProperty("user.name");
+ }
+
+ if (port <= 0) {
+ port = SshConfigFileReader.DEFAULT_PORT;
+ }
+
+ if (error) {
+ return null;
+ }
+
+ SshClient client = SshClient.setUpDefaultClient();
+ try {
+ if (SecurityUtils.isBouncyCastleRegistered()) {
+ try {
+ if (GenericUtils.isEmpty(identities)) {
+ ClientIdentity.setKeyPairProvider(client,
+ false, // not strict - even though we should...
+ true, // supportedOnly
+ new FilePasswordProvider() {
+ @Override
+ public String getPassword(String file) throws IOException {
+ stdout.print("Enter password for private key file=" + file + ": ");
+ return stdin.readLine();
+ }
+ });
+ } else {
+ AbstractFileKeyPairProvider provider = SecurityUtils.createFileKeyPairProvider();
+ provider.setFiles(identities);
+ client.setKeyPairProvider(provider);
+ }
+ } catch (Throwable t) {
+ stderr.println("Error loading user keys: " + t.getMessage());
+ }
+ }
+
+ Map<String,Object> props = client.getProperties();
+ props.putAll(options);
+
+ client.start();
+ client.setUserInteraction(new UserInteraction() {
+ @Override
+ public void welcome(String banner) {
+ stdout.println(banner);
+ }
+
+ @Override
+ public String[] interactive(String destination, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
+ int numPropmts = GenericUtils.length(prompt);
+ String[] answers = new String[numPropmts];
+ try {
+ for (int i = 0; i < numPropmts; i++) {
+ stdout.print(prompt[i] + " ");
+ answers[i] = stdin.readLine();
+ }
+ } catch (IOException e) {
+ // ignored
+ }
+ return answers;
+ }
+ });
+
+ // TODO use a configurable wait time
+ ClientSession session = client.connect(login, host, port).await().getSession();
+ try {
+ session.auth().verify(); // TODO use a configurable wait time
+ return session;
+ } catch(Exception e) {
+ session.close(true);
+ throw e;
+ }
+ } catch(Exception e) {
+ client.close();
+ throw e;
+ }
+ }
+
public static void main(String[] args) throws Exception {
Handler fh = new ConsoleHandler();
fh.setLevel(Level.FINEST);
@@ -347,93 +514,55 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
}
root.addHandler(fh);
- int port = SshConfigFileReader.DEFAULT_PORT;
- String host = null;
- String login = System.getProperty("user.name");
+ PrintStream stdout=System.out, stderr=System.err;
boolean agentForward = false;
List<String> command = null;
int logLevel = 0;
int socksPort = -1;
+ int numArgs = GenericUtils.length(args);
boolean error = false;
- List<String> identities = new ArrayList<String>();
- Map<String, String> options = new LinkedHashMap<String, String>();
-
- for (int i = 0; i < args.length; i++) {
- if (command == null && "-p".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires an argument: " + args[i]);
+ String target = null;
+ for (int i = 0; i < numArgs; i++) {
+ String argName = args[i];
+ if (command == null && "-D".equals(argName)) {
+ if (i + 1 >= numArgs) {
+ System.err.println("option requires an argument: " + argName);
error = true;
break;
}
- port = Integer.parseInt(args[++i]);
- } else if (command == null && "-D".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires an argument: " + args[i]);
+ if (socksPort > 0) {
+ stderr.println(argName + " option value re-specified: " + socksPort);
error = true;
break;
}
- socksPort = Integer.parseInt(args[++i]);
- } else if (command == null && "-l".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires an argument: " + args[i]);
+
+ if ((socksPort=Integer.parseInt(args[++i])) <= 0) {
+ stderr.println("Bad option value for " + argName + ": " + socksPort);
error = true;
break;
}
- login = args[++i];
- } else if (command == null && "-v".equals(args[i])) {
+ } else if (command == null && "-v".equals(argName)) {
logLevel += 1;
- } else if (command == null && "-vv".equals(args[i])) {
+ } else if (command == null && "-vv".equals(argName)) {
logLevel += 2;
- } else if (command == null && "-vvv".equals(args[i])) {
+ } else if (command == null && "-vvv".equals(argName)) {
logLevel += 3;
- } else if (command == null && "-A".equals(args[i])) {
+ } else if (command == null && "-A".equals(argName)) {
agentForward = true;
- } else if (command == null && "-a".equals(args[i])) {
+ } else if (command == null && "-a".equals(argName)) {
agentForward = false;
- } else if (command == null && "-i".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires and argument: " + args[i]);
- error = true;
- break;
- }
- identities.add(args[++i]);
- } else if (command == null && "-o".equals(args[i])) {
- if (i + 1 >= args.length) {
- System.err.println("option requires and argument: " + args[i]);
- error = true;
- break;
- }
- String opt = args[++i];
- int idx = opt.indexOf('=');
- if (idx <= 0) {
- System.err.println("bad syntax for option: " + opt);
- error = true;
- break;
- }
- options.put(opt.substring(0, idx), opt.substring(idx + 1));
- } else if (command == null && args[i].startsWith("-")) {
- System.err.println("illegal option: " + args[i]);
- error = true;
- break;
} else {
- if (command == null && host == null) {
- host = args[i];
+ if (command == null && target == null) {
+ target = argName;
} else {
if (command == null) {
command = new ArrayList<String>();
}
- command.add(args[i]);
+ command.add(argName);
}
}
}
- if (host == null) {
- System.err.println("hostname required");
- error = true;
- }
- if (error) {
- System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-D socksPort] [-l login] [-p port] [-o option=value] hostname [command]");
- System.exit(-1);
- }
+
if (logLevel <= 0) {
root.setLevel(Level.WARNING);
} else if (logLevel == 1) {
@@ -444,98 +573,71 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
root.setLevel(Level.FINEST);
}
- try(SshClient client = SshClient.setUpDefaultClient();
- BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
- if (SecurityUtils.isBouncyCastleRegistered()) {
- try {
- ClientIdentity.setKeyPairProvider(client,
- false, // not strict - even though we should...
- true, // supportedOnly
- new FilePasswordProvider() {
- @Override
- public String getPassword(String file) throws IOException {
- System.out.print("Enter password for private key file=" + file + ": ");
- return stdin.readLine();
- }
- });
- } catch (Throwable t) {
- System.out.println("Error loading user keys: " + t.getMessage());
+ ClientSession session=null;
+ try(BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) {
+ if (!error) {
+ if ((session=setupClientSession("-p", stdin, stdout, stderr, args)) == null) {
+ error = true;
}
}
-
- Map<String,Object> props = client.getProperties();
- props.putAll(options);
-
- client.start();
- client.setUserInteraction(new UserInteraction() {
- @Override
- public void welcome(String banner) {
- System.out.println(banner);
- }
-
- @Override
- public String[] interactive(String destination, String name, String instruction, String lang, String[] prompt, boolean[] echo) {
- String[] answers = new String[prompt.length];
- try {
- for (int i = 0; i < prompt.length; i++) {
- System.out.print(prompt[i] + " ");
- answers[i] = stdin.readLine();
- }
- } catch (IOException e) {
- // ignored
- }
- return answers;
- }
- });
- /*
- String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
- if (authSock == null && provider != null) {
- Iterable<KeyPair> keys = provider.loadKeys();
- AgentServer server = new AgentServer();
- authSock = server.start();
- SshAgent agent = new AgentClient(authSock);
- for (KeyPair key : keys) {
- agent.addIdentity(key, "");
- }
- agent.close();
- props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
+ if (error) {
+ System.err.println("usage: ssh [-A|-a] [-v[v][v]] [-D socksPort] [-l login] [-p port] [-o option=value] hostname/user@host [command]");
+ System.exit(-1);
}
- */
+
+ try(SshClient client = (SshClient) session.getFactoryManager()) {
+ /*
+ String authSock = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+ if (authSock == null && provider != null) {
+ Iterable<KeyPair> keys = provider.loadKeys();
+ AgentServer server = new AgentServer();
+ authSock = server.start();
+ SshAgent agent = new AgentClient(authSock);
+ for (KeyPair key : keys) {
+ agent.addIdentity(key, "");
+ }
+ agent.close();
+ props.put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, authSock);
+ }
+ */
- try(ClientSession session = client.connect(login, host, port).await().getSession()) {
- session.auth().verify();
-
- if (socksPort >= 0) {
- session.startDynamicPortForwarding(new SshdSocketAddress("localhost", socksPort));
- Thread.sleep(Long.MAX_VALUE);
- } else {
- ClientChannel channel;
- if (command == null) {
- channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
- ((ChannelShell) channel).setAgentForwarding(agentForward);
- channel.setIn(new NoCloseInputStream(System.in));
+ try {
+ if (socksPort >= 0) {
+ session.startDynamicPortForwarding(new SshdSocketAddress("localhost", socksPort));
+ Thread.sleep(Long.MAX_VALUE);
} else {
- StringWriter w = new StringWriter();
- for (String cmd : command) {
- w.append(cmd).append(" ");
+ ClientChannel channel;
+ if (command == null) {
+ channel = session.createChannel(ClientChannel.CHANNEL_SHELL);
+ ((ChannelShell) channel).setAgentForwarding(agentForward);
+ channel.setIn(new NoCloseInputStream(System.in));
+ } else {
+ StringWriter w = new StringWriter();
+ for (String cmd : command) {
+ w.append(cmd).append(" ");
+ }
+ w.close();
+ channel = session.createChannel(ClientChannel.CHANNEL_EXEC, w.toString());
}
- w.close();
- channel = session.createChannel(ClientChannel.CHANNEL_EXEC, w.toString());
- }
-
- try {
- channel.setOut(new NoCloseOutputStream(System.out));
- channel.setErr(new NoCloseOutputStream(System.err));
- channel.open().await(); // TODO use verify and a configurable timeout
- channel.waitFor(ClientChannel.CLOSED, 0);
- } finally {
- channel.close();
+
+ try {
+ channel.setOut(new NoCloseOutputStream(System.out));
+ channel.setErr(new NoCloseOutputStream(System.err));
+ channel.open().await(); // TODO use verify and a configurable timeout
+ channel.waitFor(ClientChannel.CLOSED, 0);
+ } finally {
+ channel.close();
+ }
+ session.close(false);
}
- session.close(false);
+ } finally {
+ client.stop();
}
} finally {
- client.stop();
+ if (session != null) {
+ session.close();
+ }
}
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 6f348da..bd1988c 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
@@ -91,10 +91,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.FileTime;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -769,7 +771,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
}
@Override
- public DirEntry[] readDir(Handle handle) throws IOException {
+ public List<DirEntry> readDir(Handle handle) throws IOException {
if (!isOpen()) {
throw new IOException("readDir(" + handle + ") client is closed");
}
@@ -779,7 +781,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
return checkDir(receive(send(SSH_FXP_READDIR, buffer)));
}
- protected DirEntry[] checkDir(Buffer buffer) throws IOException {
+ protected List<DirEntry> checkDir(Buffer buffer) throws IOException {
int length = buffer.getInt();
int type = buffer.getUByte();
int id = buffer.getInt();
@@ -796,7 +798,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
throw new SftpException(substatus, msg);
} else if (type == SSH_FXP_NAME) {
int len = buffer.getInt();
- DirEntry[] entries = new DirEntry[len];
+ List<DirEntry> entries = new ArrayList<DirEntry>(len);
for (int i = 0; i < len; i++) {
String name = buffer.getString();
int version = getVersion();
@@ -806,7 +808,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
log.trace("checkDir(id={})[{}] ({})[{}]: {}", Integer.valueOf(id), Integer.valueOf(i), name, longName, attrs);
}
- entries[i] = new DirEntry(name, longName, attrs);
+ entries.add(new DirEntry(name, longName, attrs));
}
return entries;
} else {
@@ -972,7 +974,7 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
public Iterator<DirEntry> iterator() {
return new Iterator<DirEntry>() {
private CloseableHandle handle;
- private DirEntry[] entries;
+ private List<DirEntry> entries;
private int index;
{
@@ -982,13 +984,13 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
@Override
public boolean hasNext() {
- return (entries != null) && (index < entries.length);
+ return (entries != null) && (index < entries.size());
}
@Override
public DirEntry next() {
- DirEntry entry = entries[index++];
- if (index >= entries.length) {
+ DirEntry entry = entries.get(index++);
+ if (index >= entries.size()) {
load();
}
return entry;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index 16295ba..bc3d5f5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -31,6 +31,7 @@ import java.nio.channels.Channel;
import java.nio.file.attribute.FileTime;
import java.util.Collection;
import java.util.EnumSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -280,7 +281,17 @@ public interface SftpClient extends SubsystemClient {
CloseableHandle openDir(String path) throws IOException;
- DirEntry[] readDir(Handle handle) throws IOException;
+ /**
+ * @param handle Directory {@link Handle} to read from
+ * @return A {@link List} of entries - {@code null} to indicate no more entries
+ * <B>Note:</B> the list may be <U>incomplete</U> since the client and
+ * server have some internal imposed limit on the number of entries they
+ * can process. Therefore several calls to this method may be required
+ * (until {@code null}). In order to iterate over all the entries use
+ * {@link #readDir(String)}
+ * @throws IOException If failed to access the remote site
+ */
+ List<DirEntry> readDir(Handle handle) throws IOException;
String canonicalPath(String path) throws IOException;
@@ -308,6 +319,13 @@ public interface SftpClient extends SubsystemClient {
// High level API
//
+ /**
+ * @param path The remote directory path
+ * @return An {@link Iterable} that can be used to iterate over all the
+ * directory entries (unlike {@link #readDir(Handle)})
+ * @throws IOException If failed to access the remote site
+ * @see #readDir(Handle)
+ */
Iterable<DirEntry> readDir(String path) throws IOException;
// default values used if none specified
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 a2af81f..2adb315 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
@@ -312,7 +312,7 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
}
@Override
- public DirEntry[] readDir(Handle handle) throws IOException {
+ public List<DirEntry> readDir(Handle handle) throws IOException {
if (!isOpen()) {
throw new IOException("readDir(" + handle + ") client is closed");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 23ae4f2..796f678 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
@@ -73,6 +73,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -328,20 +329,44 @@ public class SftpFileSystemProvider extends FileSystemProvider {
@Override
public Iterator<Path> iterator() {
return new Iterator<Path>() {
+ private boolean dotIgnored, dotdotIgnored;
+ private SftpClient.DirEntry curEntry = nextEntry();
+
@SuppressWarnings("synthetic-access")
private final Iterator<SftpClient.DirEntry> it = iter.iterator();
@Override
public boolean hasNext() {
- return it.hasNext();
+ return (curEntry != null);
}
@Override
public Path next() {
- SftpClient.DirEntry entry = it.next();
+ if (curEntry == null) {
+ throw new NoSuchElementException("No next entry");
+ }
+
+ SftpClient.DirEntry entry = curEntry;
+ curEntry = nextEntry();
return p.resolve(entry.filename);
}
+ private SftpClient.DirEntry nextEntry() {
+ while(it.hasNext()) {
+ SftpClient.DirEntry entry = it.next();
+ String name = entry.filename;
+ 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");
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 bb1f1e5..0ff4943 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
@@ -208,7 +208,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
protected static class DirectoryHandle extends Handle implements Iterator<Path> {
- private boolean done;
+ private boolean done, sendDotDot;
// the directory should be read once at "open directory"
private DirectoryStream<Path> ds;
private Iterator<Path> fileList;
@@ -216,6 +216,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
public DirectoryHandle(Path file) throws IOException {
super(file);
ds = Files.newDirectoryStream(file);
+
+ Path parent = file.getParent();
+ sendDotDot = (parent != null); // if no parent then no need to send ".."
fileList = ds.iterator();
}
@@ -227,6 +230,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
this.done = done;
}
+ public boolean isSendDotDot() {
+ return sendDotDot;
+ }
+
+ public void setSendDotDot(boolean sendIt) {
+ sendDotDot = sendIt;
+ }
+
@Override
public boolean hasNext() {
return fileList.hasNext();
@@ -1228,12 +1239,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
} else if (!Files.isReadable(file)) {
sendStatus(id, SSH_FX_PERMISSION_DENIED, file.toString());
} else {
- if (dh.hasNext()) {
- // There is at least one file in the directory.
+ if (dh.isSendDotDot() || dh.hasNext()) {
+ // There is at least one file in the directory or we need to send the "..".
// Send only a few files at a time to not create packets of a too
// large size or have a timeout to occur.
- sendName(id, dh);
- if (!dh.hasNext()) {
+ sendDirEntries(id, dh);
+ if ((!dh.isSendDotDot()) && (!dh.hasNext())) {
// if no more files to send
dh.setDone(true);
dh.clearFileList();
@@ -1252,11 +1263,14 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
protected void doOpenDir(Buffer buffer, int id) throws IOException {
String path = buffer.getString();
- log.debug("Received SSH_FXP_OPENDIR (path={})", path);
+ Path f = resolveFile(path);
+ Path abs = f.toAbsolutePath();
+ Path p = abs.normalize();
+ log.debug("Received SSH_FXP_OPENDIR (path={})[{}]", path, p);
+
try {
- Path p = resolveFile(path);
- LinkOption[] options = IoUtils.getLinkOptions(false);
- Boolean status = IoUtils.checkFileExists(p, options);
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ Boolean status = IoUtils.checkFileExists(p, options);
if (status == null) {
throw new AccessDeniedException("Cannot determine open-dir existence of " + p);
}
@@ -1738,7 +1752,7 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
send(buffer);
}
- protected void sendName(int id, Iterator<Path> files) throws IOException {
+ protected void sendDirEntries(int id, DirectoryHandle files) throws IOException {
Buffer buffer = new ByteArrayBuffer();
buffer.putByte((byte) SSH_FXP_NAME);
buffer.putInt(id);
@@ -1746,13 +1760,22 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
buffer.putInt(0);
int nb = 0, maxSize = FactoryManagerUtils.getIntProperty(session, MAX_PACKET_LENGTH_PROP, DEFAULT_MAX_PACKET_LENGTH);
- while (files.hasNext() && (buffer.wpos() < maxSize)) {
- Path f = files.next();
- String shortName = getShortName(f);
- buffer.putString(shortName, StandardCharsets.UTF_8);
+ while((files.isSendDotDot() || files.hasNext()) && (buffer.wpos() < maxSize)) {
+ Path f;
+ String shortName;
+ if (files.isSendDotDot()) {
+ f = files.getFile().getParent();
+ shortName = "..";
+ files.setSendDotDot(false); // do not send it again
+ } else {
+ f = files.next();
+ shortName = getShortName(f);
+ }
+
+ buffer.putString(shortName);
if (version == SFTP_V3) {
String longName = getLongName(f);
- buffer.putString(longName, StandardCharsets.UTF_8); // Format specified in the specs
+ buffer.putString(longName);
if (log.isTraceEnabled()) {
log.trace("sendName(id=" + id + ")[" + nb + "] - " + shortName + " [" + longName + "]");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java b/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
new file mode 100644
index 0000000..390960e
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/SshClientMain.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Just a test class used to invoke {@link SshClient#main(String[])} in
+ * order to have logging - which is in {@code test} scope
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SshClientMain {
+ public static void main(String[] args) throws Exception {
+ SshClient.main(args);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 3997c5b..bfefc71 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
@@ -314,6 +314,9 @@ public class SftpFileSystemTest extends BaseTestSupport {
String rootName = root.toString();
try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
for (Path child : ds) {
+ String name = child.getFileName().toString();
+ assertNotEquals("Unexpected dot name", ".", name);
+ assertNotEquals("Unexpected dotdot name", "..", name);
System.out.append('\t').append('[').append(rootName).append("] ").println(child);
}
} catch(IOException | RuntimeException e) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f7f21bc8/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 7aa686f..cf88921 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,6 +39,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -803,9 +804,23 @@ public class SftpTest extends BaseTestSupport {
}
try(SftpClient.CloseableHandle h = sftp.openDir(dir)) {
- SftpClient.DirEntry[] dirEntries = sftp.readDir(h);
+ List<SftpClient.DirEntry> dirEntries = sftp.readDir(h);
assertNotNull("No dir entries", dirEntries);
- assertEquals("Mismatced number of dir entries", 1, dirEntries.length);
+
+ boolean dotFiltered = false, dotdotFiltered = false;
+ for (Iterator<SftpClient.DirEntry> it = dirEntries.iterator(); it.hasNext(); ) {
+ SftpClient.DirEntry entry = it.next();
+ String name = entry.filename;
+ if (".".equals(name) && (!dotFiltered)) {
+ it.remove();
+ dotFiltered = true;
+ } else if ("..".equals(name) && (!dotdotFiltered)) {
+ it.remove();
+ dotdotFiltered = true;
+ }
+ }
+
+ assertEquals("Mismatched number of dir entries", 1, dirEntries.size());
assertNull("Unexpected entry read", sftp.readDir(h));
}
@@ -832,9 +847,17 @@ public class SftpTest extends BaseTestSupport {
assertTrue("Test directory not reported as such", attributes.isDirectory());
int nb = 0;
+ boolean dotFiltered = false, dotdotFiltered = false;
for (SftpClient.DirEntry entry : sftp.readDir(dir)) {
assertNotNull("Unexpected null entry", entry);
- nb++;
+ String name = entry.filename;
+ if (".".equals(name) && (!dotFiltered)) {
+ dotFiltered = true;
+ } else if ("..".equals(name) && (!dotdotFiltered)) {
+ dotdotFiltered = true;
+ } else {
+ nb++;
+ }
}
assertEquals("Mismatched read dir entries", 1, nb);