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 2013/07/26 10:37:27 UTC
[2/3] git commit: [SSHD-90] Fix some problems with SCP client Support
for attributes preservation on copy
[SSHD-90] Fix some problems with SCP client
Support for attributes preservation on copy
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/725bcd9f
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/725bcd9f
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/725bcd9f
Branch: refs/heads/master
Commit: 725bcd9f2fc7c0027eaec7c30e439b375f088254
Parents: f31ac81
Author: Guillaume Nodet <gn...@apache.org>
Authored: Thu Jul 25 23:49:52 2013 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Thu Jul 25 23:49:52 2013 +0200
----------------------------------------------------------------------
.../java/org/apache/sshd/client/ScpClient.java | 18 +-
.../client/channel/AbstractClientChannel.java | 4 +
.../sshd/client/scp/DefaultScpClient.java | 105 +++++---
.../common/channel/ChannelPipedInputStream.java | 2 +-
.../org/apache/sshd/common/scp/ScpHelper.java | 189 +++++++++++--
.../apache/sshd/server/command/ScpCommand.java | 25 +-
.../sshd/server/command/ScpCommandFactory.java | 32 +--
.../src/test/java/org/apache/sshd/ScpTest.java | 263 +++++++++++++++++++
8 files changed, 518 insertions(+), 120 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
index d2bdaa8..381a210 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java
@@ -24,18 +24,18 @@ import java.io.IOException;
*/
public interface ScpClient {
- void download(String remote, String local) throws IOException;
+ enum Option {
+ Recursive,
+ PreserveAttributes,
+ TargetIsDirectory
+ }
- void download(String remote, String local, boolean recursive) throws IOException;
+ void download(String remote, String local, Option... options) throws IOException;
- void download(String[] remote, String local) throws Exception;
+ void download(String[] remote, String local, Option... options) throws Exception;
- void download(String[] remote, String local, boolean recursive) throws Exception;
+ void upload(String local, String remote, Option... options) throws IOException;
- void upload(String remote, String local) throws IOException;
-
- void upload(String remote, String local, boolean recursive) throws IOException;
-
- void upload(String[] local, String remote, boolean recursive) throws IOException;
+ void upload(String[] local, String remote, Option... options) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
index 8d04dba..b786c39 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/AbstractClientChannel.java
@@ -227,6 +227,10 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C
}
protected void doWriteData(byte[] data, int off, int len) throws IOException {
+ // If we're already closing, ignore incoming data
+ if (closing.get()) {
+ return;
+ }
if (out != null) {
out.write(data, off, len);
out.flush();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index 1b590c2..911eb5c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -20,7 +20,11 @@ package org.apache.sshd.client.scp;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.ScpClient;
@@ -42,39 +46,44 @@ public class DefaultScpClient implements ScpClient {
this.clientSession = clientSession;
}
- public void download(String remote, String local) throws IOException {
- download(new String[] { remote }, local, false, false);
- }
-
- public void download(String remote, String local, boolean recursive) throws IOException {
- download(new String[] { remote }, local, recursive, false);
- }
-
- public void download(String[] remote, String local) throws IOException {
- download(remote, local, false, true);
+ public void download(String remote, String local, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ download(remote, local, Arrays.asList(options));
}
- public void download(String[] remote, String local, boolean recursive) throws IOException {
- download(remote, local, recursive, true);
+ public void download(String[] remote, String local, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ List<Option> opts = options(options);
+ if (remote.length > 1) {
+ opts.add(Option.TargetIsDirectory);
+ }
+ for (String r : remote) {
+ download(r, local, opts);
+ }
}
- private void download(String[] remote, String local, boolean recursive, boolean shouldBeDir) throws IOException {
+ protected void download(String remote, String local, Collection<Option> options) throws IOException {
local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+
StringBuilder sb = new StringBuilder("scp");
- if (recursive) {
+ if (options.contains(Option.Recursive)) {
sb.append(" -r");
}
- sb.append(" -f");
- for (String r : remote) {
- r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}");
- sb.append(" ").append(r);
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
}
+ sb.append(" -f");
+ sb.append(" --");
+ sb.append(" ");
+ sb.append(remote);
FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
FileSystemView fs = factory.createFileSystemView(clientSession);
SshFile target = fs.getFile(local);
- if (shouldBeDir) {
+ if (options.contains(Option.TargetIsDirectory)) {
if (!target.doesExist()) {
throw new SshException("Target directory " + target.toString() + " does not exists");
}
@@ -92,42 +101,47 @@ public class DefaultScpClient implements ScpClient {
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
- helper.receive(target, recursive, shouldBeDir);
+ helper.receive(target,
+ options.contains(Option.Recursive),
+ options.contains(Option.TargetIsDirectory),
+ options.contains(Option.PreserveAttributes));
channel.close(false);
}
- public void upload(String remote, String local) throws IOException {
- upload(new String[] { remote }, local, false, false);
- }
-
- public void upload(String remote, String local, boolean recursive) throws IOException {
- upload(new String[] { remote }, local, recursive, false);
- }
-
- public void upload(String[] local, String remote) throws IOException {
- upload(local, remote, false, true);
+ public void upload(String local, String remote, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ upload(new String[] { local }, remote, options(options));
}
- public void upload(String[] local, String remote, boolean recursive) throws IOException {
- upload(local, remote, false, true);
+ public void upload(String[] local, String remote, Option... options) throws IOException {
+ local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
+ remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
+ List<Option> opts = options(options);
+ if (local.length > 1) {
+ opts.add(Option.TargetIsDirectory);
+ }
+ upload(local, remote, opts);
}
- private void upload(String[] local, String remote, boolean recursive, boolean shouldBeDir) throws IOException {
+ protected void upload(String[] local, String remote, Collection<Option> options) throws IOException {
local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}");
remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}");
StringBuilder sb = new StringBuilder("scp");
- if (recursive) {
+ if (options.contains(Option.Recursive)) {
sb.append(" -r");
}
- if (shouldBeDir) {
+ if (options.contains(Option.TargetIsDirectory)) {
sb.append(" -d");
}
- sb.append(" -t");
- for (String r : local) {
- r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}");
- sb.append(" ").append(r);
+ if (options.contains(Option.PreserveAttributes)) {
+ sb.append(" -p");
}
+ sb.append(" -t");
+ sb.append(" --");
+ sb.append(" ");
+ sb.append(remote);
ChannelExec channel = clientSession.createExecChannel(sb.toString());
try {
channel.open().await();
@@ -138,13 +152,22 @@ public class DefaultScpClient implements ScpClient {
FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory();
FileSystemView fs = factory.createFileSystemView(clientSession);
ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs);
- SshFile target = fs.getFile(remote);
- helper.send(Arrays.asList(local), recursive);
+ helper.send(Arrays.asList(local),
+ options.contains(Option.Recursive),
+ options.contains(Option.PreserveAttributes));
channel.close(false);
}
+ private List<Option> options(Option... options) {
+ List<Option> opts = new ArrayList<Option>();
+ if (options != null) {
+ opts.addAll(Arrays.asList(options));
+ }
+ return opts;
+ }
+
private <T> T checkNotNull(T t, String message) {
if (t == null) {
throw new IllegalStateException(String.format(message, t));
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
index e68d074..1d7b489 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/ChannelPipedInputStream.java
@@ -96,7 +96,7 @@ public class ChannelPipedInputStream extends InputStream {
lock.lock();
try {
for (;;) {
- if (closed) {
+ if (closed && !writerClosed) {
throw new IOException("Pipe closed");
}
if (buffer.available() > 0) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 17b55c1..b9198f3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -23,7 +23,10 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.EnumSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.file.FileSystemView;
@@ -43,6 +46,16 @@ public class ScpHelper {
public static final int WARNING = 1;
public static final int ERROR = 2;
+ public static final int S_IRUSR = 0000400;
+ public static final int S_IWUSR = 0000200;
+ public static final int S_IXUSR = 0000100;
+ public static final int S_IRGRP = 0000040;
+ public static final int S_IWGRP = 0000020;
+ public static final int S_IXGRP = 0000010;
+ public static final int S_IROTH = 0000004;
+ public static final int S_IWOTH = 0000002;
+ public static final int S_IXOTH = 0000001;
+
protected final FileSystemView root;
protected final InputStream in;
protected final OutputStream out;
@@ -53,7 +66,7 @@ public class ScpHelper {
this.root = root;
}
- public void receive(SshFile path, boolean recursive, boolean shouldBeDir) throws IOException {
+ public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve) throws IOException {
if (shouldBeDir) {
if (!path.doesExist()) {
throw new SshException("Target directory " + path.toString() + " does not exists");
@@ -63,6 +76,7 @@ public class ScpHelper {
}
}
ack();
+ long[] time = null;
for (;;)
{
String line;
@@ -76,13 +90,17 @@ public class ScpHelper {
isDir = true;
case 'C':
line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
break;
case 'T':
- readLine();
+ line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
+ time = parseTime(line);
ack();
continue;
case 'E':
- readLine();
+ line = ((char) c) + readLine();
+ log.debug("Received header: " + line);
return;
default:
//a real ack that has been acted upon already
@@ -91,19 +109,21 @@ public class ScpHelper {
if (recursive && isDir)
{
- receiveDir(line, path);
+ receiveDir(line, path, time, preserve);
+ time = null;
}
else
{
- receiveFile(line, path);
+ receiveFile(line, path, time, preserve);
+ time = null;
}
}
}
- public void receiveDir(String header, SshFile path) throws IOException {
+ public void receiveDir(String header, SshFile path, long[] time, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Writing dir {}", path);
+ log.debug("Receiving directory {}", path);
}
if (!header.startsWith("D")) {
throw new IOException("Expected a D message but got '" + header + "'");
@@ -128,20 +148,34 @@ public class ScpHelper {
throw new IOException("Could not create directory " + file);
}
+ if (preserve) {
+ Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+ attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+ if (time != null) {
+ attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
+ attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+ }
+ file.setAttributes(attrs);
+ }
+
ack();
+ time = null;
for (;;) {
header = readLine();
+ log.debug("Received header: " + header);
if (header.startsWith("C")) {
- receiveFile(header, file);
+ receiveFile(header, file, time, preserve);
+ time = null;
} else if (header.startsWith("D")) {
- receiveDir(header, file);
+ receiveDir(header, file, time, preserve);
+ time = null;
} else if (header.equals("E")) {
ack();
break;
- } else if (header.equals("T")) {
+ } else if (header.startsWith("T")) {
+ time = parseTime(header);
ack();
- break;
} else {
throw new IOException("Unexpected message: '" + header + "'");
}
@@ -149,9 +183,9 @@ public class ScpHelper {
}
- public void receiveFile(String header, SshFile path) throws IOException {
+ public void receiveFile(String header, SshFile path, long[] time, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Writing file {}", path);
+ log.debug("Receiving file {}", path);
}
if (!header.startsWith("C")) {
throw new IOException("Expected a C message but got '" + header + "'");
@@ -194,6 +228,16 @@ public class ScpHelper {
os.close();
}
+ if (preserve) {
+ Map<SshFile.Attribute, Object> attrs = new HashMap<SshFile.Attribute, Object>();
+ attrs.put(SshFile.Attribute.Permissions, fromOctalPerms(perms));
+ if (time != null) {
+ attrs.put(SshFile.Attribute.LastModifiedTime, time[0]);
+ attrs.put(SshFile.Attribute.LastAccessTime, time[1]);
+ }
+ file.setAttributes(attrs);
+ }
+
ack();
readAck(false);
}
@@ -219,7 +263,7 @@ public class ScpHelper {
}
}
- public void send(List<String> paths, boolean recursive) throws IOException {
+ public void send(List<String> paths, boolean recursive, boolean preserve) throws IOException {
for (String pattern : paths) {
int idx = pattern.indexOf('*');
if (idx >= 0) {
@@ -233,13 +277,13 @@ public class ScpHelper {
for (String path : included) {
SshFile file = root.getFile(basedir + "/" + path);
if (file.isFile()) {
- sendFile(file);
+ sendFile(file, preserve);
} else if (file.isDirectory()) {
if (!recursive) {
out.write(ScpHelper.WARNING);
out.write((path + " not a regular file\n").getBytes());
} else {
- sendDir(file);
+ sendDir(file, preserve);
}
} else {
out.write(ScpHelper.WARNING);
@@ -258,12 +302,12 @@ public class ScpHelper {
throw new IOException(file + ": no such file or directory");
}
if (file.isFile()) {
- sendFile(file);
+ sendFile(file, preserve);
} else if (file.isDirectory()) {
if (!recursive) {
throw new IOException(file + " not a regular file");
} else {
- sendDir(file);
+ sendDir(file, preserve);
}
} else {
throw new IOException(file + ": unknown file type");
@@ -272,15 +316,33 @@ public class ScpHelper {
}
}
- public void sendFile(SshFile path) throws IOException {
+ public void sendFile(SshFile path, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Reading file {}", path);
+ log.debug("Sending file {}", path);
+ }
+
+ Map<SshFile.Attribute,Object> attrs = path.getAttributes(true);
+ if (preserve) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("T");
+ buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append(" ");
+ buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append("\n");
+ out.write(buf.toString().getBytes());
+ out.flush();
+ readAck(false);
}
+
StringBuffer buf = new StringBuffer();
buf.append("C");
- buf.append("0644"); // TODO: what about perms
+ buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0644");
buf.append(" ");
- buf.append(path.getSize()); // length
+ buf.append(attrs.get(SshFile.Attribute.Size)); // length
buf.append(" ");
buf.append(path.getName());
buf.append("\n");
@@ -305,13 +367,30 @@ public class ScpHelper {
readAck(false);
}
- public void sendDir(SshFile path) throws IOException {
+ public void sendDir(SshFile path, boolean preserve) throws IOException {
if (log.isDebugEnabled()) {
- log.debug("Reading directory {}", path);
+ log.debug("Sending directory {}", path);
+ }
+ Map<SshFile.Attribute,Object> attrs = path.getAttributes(true);
+ if (preserve) {
+ StringBuffer buf = new StringBuffer();
+ buf.append("T");
+ buf.append(attrs.get(SshFile.Attribute.LastModifiedTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append(" ");
+ buf.append(attrs.get(SshFile.Attribute.LastAccessTime));
+ buf.append(" ");
+ buf.append("0");
+ buf.append("\n");
+ out.write(buf.toString().getBytes());
+ out.flush();
+ readAck(false);
}
+
StringBuffer buf = new StringBuffer();
buf.append("D");
- buf.append("0755"); // what about perms
+ buf.append(preserve ? toOctalPerms((EnumSet<SshFile.Permission>) attrs.get(SshFile.Attribute.Permissions)) : "0755");
buf.append(" ");
buf.append("0"); // length
buf.append(" ");
@@ -323,9 +402,9 @@ public class ScpHelper {
for (SshFile child : path.listSshFiles()) {
if (child.isFile()) {
- sendFile(child);
+ sendFile(child, preserve);
} else if (child.isDirectory()) {
- sendDir(child);
+ sendDir(child, preserve);
}
}
@@ -334,6 +413,62 @@ public class ScpHelper {
readAck(false);
}
+ private long[] parseTime(String line) {
+ String[] numbers = line.substring(1).split(" ");
+ return new long[] { Long.parseLong(numbers[0]), Long.parseLong(numbers[2]) };
+ }
+
+ public static String toOctalPerms(EnumSet<SshFile.Permission> perms) {
+ int pf = 0;
+ for (SshFile.Permission p : perms) {
+ switch (p) {
+ case UserRead: pf |= S_IRUSR; break;
+ case UserWrite: pf |= S_IWUSR; break;
+ case UserExecute: pf |= S_IXUSR; break;
+ case GroupRead: pf |= S_IRGRP; break;
+ case GroupWrite: pf |= S_IWGRP; break;
+ case GroupExecute: pf |= S_IXGRP; break;
+ case OthersRead: pf |= S_IROTH; break;
+ case OthersWrite: pf |= S_IWOTH; break;
+ case OthersExecute: pf |= S_IXOTH; break;
+ }
+ }
+ return String.format("%04o", pf);
+ }
+
+ public static EnumSet<SshFile.Permission> fromOctalPerms(String str) {
+ int perms = Integer.parseInt(str, 8);
+ EnumSet<SshFile.Permission> p = EnumSet.noneOf(SshFile.Permission.class);
+ if ((perms & S_IRUSR) != 0) {
+ p.add(SshFile.Permission.UserRead);
+ }
+ if ((perms & S_IWUSR) != 0) {
+ p.add(SshFile.Permission.UserWrite);
+ }
+ if ((perms & S_IXUSR) != 0) {
+ p.add(SshFile.Permission.UserExecute);
+ }
+ if ((perms & S_IRGRP) != 0) {
+ p.add(SshFile.Permission.GroupRead);
+ }
+ if ((perms & S_IWGRP) != 0) {
+ p.add(SshFile.Permission.GroupWrite);
+ }
+ if ((perms & S_IXGRP) != 0) {
+ p.add(SshFile.Permission.GroupExecute);
+ }
+ if ((perms & S_IROTH) != 0) {
+ p.add(SshFile.Permission.OthersRead);
+ }
+ if ((perms & S_IWOTH) != 0) {
+ p.add(SshFile.Permission.OthersWrite);
+ }
+ if ((perms & S_IXOTH) != 0) {
+ p.add(SshFile.Permission.OthersExecute);
+ }
+ return p;
+ }
+
public void ack() throws IOException {
out.write(0);
out.flush();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
index 8ecd7cd..d67715c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.apache.sshd.common.file.FileSystemAware;
@@ -52,19 +53,17 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
protected boolean optD;
protected boolean optP; // TODO: handle modification times
protected FileSystemView root;
- protected List<String> paths;
+ protected String path;
protected InputStream in;
protected OutputStream out;
protected OutputStream err;
protected ExitCallback callback;
protected IOException error;
- public ScpCommand(String[] args) {
- name = Arrays.asList(args).toString();
- if (log.isDebugEnabled()) {
- log.debug("Executing command {}", name);
- }
- paths = new ArrayList<String>();
+ public ScpCommand(String command) {
+ this.name = command;
+ log.debug("Executing command {}", command);
+ String[] args = command.split(" ");
for (int i = 1; i < args.length; i++) {
if (args[i].charAt(0) == '-') {
for (int j = 1; j < args[i].length(); j++) {
@@ -89,16 +88,14 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
// return;
}
}
- } else if (i == args.length - 1) {
- paths.add(args[args.length - 1]);
+ } else {
+ path = command.substring(command.indexOf(args[i-1]) + args[i-1].length() + 1);
+ break;
}
}
if (!optF && !optT) {
error = new IOException("Either -f or -t option should be set");
}
- if (optT && paths.size() != 1) {
- error = new IOException("One and only one path must be given with -t option");
- }
}
public void setInputStream(InputStream in) {
@@ -137,9 +134,9 @@ public class ScpCommand implements Command, Runnable, FileSystemAware {
ScpHelper helper = new ScpHelper(in, out, root);
try {
if (optT) {
- helper.receive(root.getFile(paths.get(0)), optR, optD);
+ helper.receive(root.getFile(path), optR, optD, optP);
} else if (optF) {
- helper.send(paths, optR);
+ helper.send(Collections.singletonList(path), optR, optP);
} else {
throw new IOException("Unsupported mode");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
index 61bded0..51eee6c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java
@@ -55,7 +55,10 @@ public class ScpCommandFactory implements CommandFactory {
*/
public Command createCommand(String command) {
try {
- return new ScpCommand(splitCommandString(command));
+ if (!command.startsWith("scp")) {
+ throw new IllegalArgumentException("Unknown command, does not begin with 'scp'");
+ }
+ return new ScpCommand(command);
} catch (IllegalArgumentException iae) {
if (delegate != null) {
return delegate.createCommand(command);
@@ -64,31 +67,4 @@ public class ScpCommandFactory implements CommandFactory {
}
}
- private String[] splitCommandString(String command) {
- if (!command.trim().startsWith("scp")) {
- throw new IllegalArgumentException("Unknown command, does not begin with 'scp'");
- }
-
- String[] args = command.split(" ");
- List<String> parts = new ArrayList<String>();
- parts.add(args[0]);
- for (int i = 1; i < args.length; i++) {
- if (!args[i].trim().startsWith("-")) {
- parts.add(concatenateWithSpace(args, i));
- break;
- } else {
- parts.add(args[i]);
- }
- }
- return parts.toArray(new String[parts.size()]);
- }
-
- private String concatenateWithSpace(String[] args, int from) {
- StringBuilder sb = new StringBuilder();
-
- for (int i = from; i < args.length; i++) {
- sb.append(args[i] + " ");
- }
- return sb.toString().trim();
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/725bcd9f/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
index 835b931..19572b6 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
@@ -20,11 +20,13 @@ package org.apache.sshd;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.Properties;
+import java.util.concurrent.TimeUnit;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
@@ -33,6 +35,7 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Logger;
import com.jcraft.jsch.UserInfo;
+import org.apache.sshd.client.ScpClient;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.util.BogusPasswordAuthenticator;
import org.apache.sshd.util.EchoShellFactory;
@@ -45,6 +48,7 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* Test for SCP support.
@@ -62,6 +66,7 @@ public class ScpTest {
ServerSocket s = new ServerSocket(0);
port = s.getLocalPort();
s.close();
+// port = 8102;
sshd = SshServer.setUpDefaultServer();
sshd.setPort(port);
@@ -123,6 +128,264 @@ public class ScpTest {
}
@Test
+ public void testScpNativeOnSingleFile() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ assertTrue(root.exists());
+
+
+ writeFile(new File("target/scp/local/out.txt"), data);
+ try {
+ scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // ok
+ }
+ new File(root, "remote").mkdirs();
+ scp.upload("target/scp/local/out.txt", "target/scp/remote/out.txt");
+ assertFileLength(new File("target/scp/remote/out.txt"), data.length(), 5000);
+
+ scp.download("target/scp/remote/out.txt", "target/scp/local/out2.txt");
+ assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnMultipleFiles() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ new File(root, "remote").mkdirs();
+ assertTrue(root.exists());
+
+
+ writeFile(new File("target/scp/local/out1.txt"), data);
+ writeFile(new File("target/scp/local/out2.txt"), data);
+ try {
+ scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/out.txt");
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // Ok
+ }
+ writeFile(new File("target/scp/remote/out.txt"), data);
+ try {
+ scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/out.txt");
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // Ok
+ }
+ new File(root, "remote/dir").mkdirs();
+ scp.upload(new String[] { "target/scp/local/out1.txt", "target/scp/local/out2.txt" }, "target/scp/remote/dir");
+ assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+ try {
+ scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/out1.txt");
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // Ok
+ }
+ try {
+ scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/dir");
+ fail("Expected IOException");
+ } catch (IOException e) {
+ // Ok
+ }
+ new File(root, "local/dir").mkdirs();
+ scp.download(new String[] { "target/scp/remote/dir/out1.txt", "target/scp/remote/dir/out2.txt" }, "target/scp/local/dir");
+ assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnRecursiveDirs() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ new File(root, "remote").mkdirs();
+ assertTrue(root.exists());
+
+ new File("target/scp/local/dir").mkdirs();
+ writeFile(new File("target/scp/local/dir/out1.txt"), data);
+ writeFile(new File("target/scp/local/dir/out2.txt"), data);
+ scp.upload("target/scp/local/dir", "target/scp/remote/", ScpClient.Option.Recursive);
+ assertFileLength(new File("target/scp/remote/dir/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+ Utils.deleteRecursive(new File("target/scp/local/dir"));
+ scp.download("target/scp/remote/dir", "target/scp/local", ScpClient.Option.Recursive);
+ assertFileLength(new File("target/scp/local/dir/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnDirWithPattern() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ new File(root, "remote").mkdirs();
+ assertTrue(root.exists());
+
+ writeFile(new File("target/scp/local/out1.txt"), data);
+ writeFile(new File("target/scp/local/out2.txt"), data);
+ scp.upload("target/scp/local/*", "target/scp/remote/");
+ assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/remote/out2.txt"), data.length(), 5000);
+
+ new File("target/scp/local/out1.txt").delete();
+ new File("target/scp/local/out2.txt").delete();
+ scp.download("target/scp/remote/*", "target/scp/local");
+ assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/local/out2.txt"), data.length(), 5000);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativeOnMixedDirAndFiles() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ new File(root, "remote").mkdirs();
+ assertTrue(root.exists());
+
+ new File("target/scp/local/dir").mkdirs();
+ writeFile(new File("target/scp/local/out1.txt"), data);
+ writeFile(new File("target/scp/local/dir/out2.txt"), data);
+ scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive);
+ assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+
+ Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
+ Utils.deleteRecursive(new File("target/scp/local/dir"));
+ scp.download("target/scp/remote/*", "target/scp/local");
+ assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+ assertFalse(new File("target/scp/local/dir/out2.txt").exists());
+
+ Utils.deleteRecursive(new File("target/scp/local/out1.txt"));
+ scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive);
+ assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+ assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+
+ session.close(false);
+ client.stop();
+ }
+
+ @Test
+ public void testScpNativePreserveAttributes() throws Exception {
+ SshClient client = SshClient.setUpDefaultClient();
+ client.start();
+ ClientSession session = client.connect("localhost", port).await().getSession();
+ session.authPassword("test", "test").await();
+
+ ScpClient scp = session.createScpClient();
+
+ String data = "0123456789\n";
+
+ File root = new File("target/scp");
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ new File(root, "local").mkdirs();
+ new File(root, "remote").mkdirs();
+ assertTrue(root.exists());
+
+ new File("target/scp/local/dir").mkdirs();
+ long lastMod = new File("target/scp/local/dir").lastModified() - TimeUnit.DAYS.toMillis(1);
+
+ writeFile(new File("target/scp/local/out1.txt"), data);
+ writeFile(new File("target/scp/local/dir/out2.txt"), data);
+ new File("target/scp/local/out1.txt").setLastModified(lastMod);
+ new File("target/scp/local/out1.txt").setExecutable(true, true);
+ new File("target/scp/local/out1.txt").setWritable(false, false);
+ new File("target/scp/local/dir/out2.txt").setLastModified(lastMod);
+ scp.upload("target/scp/local/*", "target/scp/remote/", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+ assertFileLength(new File("target/scp/remote/out1.txt"), data.length(), 5000);
+ assertEquals(lastMod, new File("target/scp/remote/out1.txt").lastModified());
+ assertFileLength(new File("target/scp/remote/dir/out2.txt"), data.length(), 5000);
+ assertEquals(lastMod, new File("target/scp/remote/dir/out2.txt").lastModified());
+
+ Utils.deleteRecursive(new File("target/scp/local"));
+ new File("target/scp/local").mkdirs();
+ scp.download("target/scp/remote/*", "target/scp/local", ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+ assertFileLength(new File("target/scp/local/out1.txt"), data.length(), 5000);
+ assertEquals(lastMod, new File("target/scp/local/out1.txt").lastModified());
+ assertFileLength(new File("target/scp/local/dir/out2.txt"), data.length(), 5000);
+ assertEquals(lastMod, new File("target/scp/local/dir/out2.txt").lastModified());
+
+ session.close(false);
+ client.stop();
+ }
+
+ private void writeFile(File file, String data) throws IOException {
+ FileOutputStream fos = new FileOutputStream(file);
+ try {
+ fos.write(data.getBytes());
+ } finally {
+ fos.close();
+ }
+ }
+
+ @Test
public void testScp() throws Exception {
session = getJschSession();