Provide an SCP client Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/16fc6a80 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/16fc6a80 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/16fc6a80
Branch: refs/heads/master Commit: 16fc6a80714f2322479eaa5dbffea4e6f179a887 Parents: c91e7de Author: Guillaume Nodet <[email protected]> Authored: Sun Jul 21 21:56:59 2013 +0200 Committer: Guillaume Nodet <[email protected]> Committed: Sun Jul 21 21:56:59 2013 +0200 ---------------------------------------------------------------------- .../java/org/apache/sshd/ClientChannel.java | 4 + .../java/org/apache/sshd/ClientSession.java | 6 + .../main/java/org/apache/sshd/SshServer.java | 3 +- .../java/org/apache/sshd/client/ScpClient.java | 23 ++ .../client/channel/AbstractClientChannel.java | 10 + .../sshd/client/channel/ChannelSession.java | 15 +- .../sshd/client/scp/DefaultScpClient.java | 151 ++++++++ .../sshd/client/session/ClientSessionImpl.java | 6 + .../org/apache/sshd/common/FactoryManager.java | 4 +- .../org/apache/sshd/common/scp/ScpHelper.java | 363 +++++++++++++++++++ .../apache/sshd/server/command/ScpCommand.java | 331 ++--------------- 11 files changed, 601 insertions(+), 315 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/ClientChannel.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/ClientChannel.java index d19d12f..4b01b0a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/ClientChannel.java +++ b/sshd-core/src/main/java/org/apache/sshd/ClientChannel.java @@ -56,6 +56,10 @@ public interface ClientChannel { */ OutputStream getInvertedIn(); + InputStream getInvertedOut(); + + InputStream getInvertedErr(); + /** * Set an input stream that will be read by this channel and forwarded to * the remote channel. Note that using such a stream will create an additional http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/ClientSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java index 8cd7f22..6b7b1af 100644 --- a/sshd-core/src/main/java/org/apache/sshd/ClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/ClientSession.java @@ -23,6 +23,7 @@ import java.security.KeyPair; import java.util.Map; import org.apache.sshd.client.ClientFactoryManager; +import org.apache.sshd.client.ScpClient; import org.apache.sshd.client.channel.ChannelDirectTcpip; import org.apache.sshd.client.channel.ChannelExec; import org.apache.sshd.client.channel.ChannelShell; @@ -112,6 +113,11 @@ public interface ClientSession extends Session { ChannelDirectTcpip createDirectTcpipChannel(SshdSocketAddress local, SshdSocketAddress remote) throws IOException; /** + * Create an SCP client from this session. + */ + ScpClient createScpClient(); + + /** * Start forwarding the given local address on the client to the given address on the server. */ SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/SshServer.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/SshServer.java index 4edab9b..acc1ca1 100644 --- a/sshd-core/src/main/java/org/apache/sshd/SshServer.java +++ b/sshd-core/src/main/java/org/apache/sshd/SshServer.java @@ -60,6 +60,7 @@ import org.apache.sshd.common.cipher.ARCFOUR256; import org.apache.sshd.common.cipher.BlowfishCBC; import org.apache.sshd.common.cipher.TripleDESCBC; import org.apache.sshd.common.compression.CompressionNone; +import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory; import org.apache.sshd.common.forward.DefaultForwardingAcceptorFactory; import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory; import org.apache.sshd.common.forward.TcpipServerChannel; @@ -79,7 +80,6 @@ import org.apache.sshd.common.util.OsUtils; import org.apache.sshd.common.util.SecurityUtils; import org.apache.sshd.server.Command; import org.apache.sshd.server.CommandFactory; -import org.apache.sshd.common.file.FileSystemFactory; import org.apache.sshd.server.PasswordAuthenticator; import org.apache.sshd.server.PublickeyAuthenticator; import org.apache.sshd.server.ServerFactoryManager; @@ -90,7 +90,6 @@ import org.apache.sshd.server.auth.UserAuthPublicKey; import org.apache.sshd.server.auth.gss.GSSAuthenticator; import org.apache.sshd.server.auth.gss.UserAuthGSS; import org.apache.sshd.server.channel.ChannelSession; -import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory; import org.apache.sshd.server.kex.DHG1; import org.apache.sshd.server.kex.DHG14; import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/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 new file mode 100644 index 0000000..fc6bfb8 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/ScpClient.java @@ -0,0 +1,23 @@ +package org.apache.sshd.client; + +import java.io.IOException; + +/** + */ +public interface ScpClient { + + void download(String remote, String local) throws IOException; + + void download(String remote, String local, boolean recursive) throws IOException; + + void download(String[] remote, String local) throws Exception; + + void download(String[] remote, String local, boolean recursive) throws Exception; + + 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; + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/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 715ae82..40015c3 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 @@ -45,7 +45,9 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C protected InputStream in; protected OutputStream invertedIn; protected OutputStream out; + protected InputStream invertedOut; protected OutputStream err; + protected InputStream invertedErr; protected Integer exitStatus; protected String exitSignal; protected int openFailureReason; @@ -68,6 +70,10 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C this.in = in; } + public InputStream getInvertedOut() { + return invertedOut; + } + public OutputStream getOut() { return out; } @@ -76,6 +82,10 @@ public abstract class AbstractClientChannel extends AbstractChannel implements C this.out = out; } + public InputStream getInvertedErr() { + return invertedErr; + } + public OutputStream getErr() { return err; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java index fe2ef23..ee48e0d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelSession.java @@ -24,6 +24,8 @@ import java.io.InputStream; import org.apache.sshd.client.future.OpenFuture; import org.apache.sshd.common.SshConstants; import org.apache.sshd.common.channel.ChannelOutputStream; +import org.apache.sshd.common.channel.ChannelPipedInputStream; +import org.apache.sshd.common.channel.ChannelPipedOutputStream; import org.apache.sshd.common.util.Buffer; /** @@ -41,8 +43,17 @@ public class ChannelSession extends AbstractClientChannel { public OpenFuture open() throws IOException { invertedIn = new ChannelOutputStream(this, remoteWindow, log, SshConstants.Message.SSH_MSG_CHANNEL_DATA); - if (out == null || err == null) { - throw new IllegalStateException("in, out and err streams should be set before opening channel"); + if (out == null) { + ChannelPipedInputStream pis = new ChannelPipedInputStream(localWindow); + ChannelPipedOutputStream pos = new ChannelPipedOutputStream(pis); + out = pos; + invertedOut = pis; + } + if (err == null) { + ChannelPipedInputStream pis = new ChannelPipedInputStream(localWindow); + ChannelPipedOutputStream pos = new ChannelPipedOutputStream(pis); + err = pos; + invertedErr = pis; } return internalOpen(); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/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 new file mode 100644 index 0000000..2bd6985 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java @@ -0,0 +1,151 @@ +package org.apache.sshd.client.scp; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.Arrays; + +import org.apache.sshd.ClientSession; +import org.apache.sshd.client.ScpClient; +import org.apache.sshd.client.channel.ChannelExec; +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.file.FileSystemFactory; +import org.apache.sshd.common.file.FileSystemView; +import org.apache.sshd.common.file.SshFile; +import org.apache.sshd.common.scp.ScpHelper; + +/** + */ +public class DefaultScpClient implements ScpClient { + + private final ClientSession clientSession; + + public DefaultScpClient(ClientSession clientSession) { + 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, boolean recursive) throws IOException { + download(remote, local, recursive, true); + } + + private void download(String[] remote, String local, boolean recursive, boolean shouldBeDir) throws IOException { + local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}"); + remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}"); + StringBuilder sb = new StringBuilder("scp"); + if (recursive) { + sb.append(" -r"); + } + sb.append(" -f"); + for (String r : remote) { + r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}"); + sb.append(" ").append(r); + } + + FileSystemFactory factory = clientSession.getFactoryManager().getFileSystemFactory(); + FileSystemView fs = factory.createFileSystemView(clientSession); + SshFile target = fs.getFile(local); + if (shouldBeDir) { + if (!target.doesExist()) { + throw new SshException("Target directory " + target.toString() + " does not exists"); + } + if (!target.isDirectory()) { + throw new SshException("Target directory " + target.toString() + " is not a directory"); + } + } + + ChannelExec channel = clientSession.createExecChannel(sb.toString()); + try { + channel.open().await(); + } catch (InterruptedException e) { + throw (IOException) new InterruptedIOException().initCause(e); + } + + ScpHelper helper = new ScpHelper(channel.getInvertedOut(), channel.getInvertedIn(), fs); + + helper.receive(target, recursive, shouldBeDir); + + 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, boolean recursive) throws IOException { + upload(local, remote, false, true); + } + + private void upload(String[] local, String remote, boolean recursive, boolean shouldBeDir) throws IOException { + local = checkNotNullAndNotEmpty(local, "Invalid argument local: {}"); + remote = checkNotNullAndNotEmpty(remote, "Invalid argument remote: {}"); + StringBuilder sb = new StringBuilder("scp"); + if (recursive) { + sb.append(" -r"); + } + if (shouldBeDir) { + sb.append(" -d"); + } + sb.append(" -t"); + for (String r : local) { + r = checkNotNullAndNotEmpty(r, "Invalid argument remote: {}"); + sb.append(" ").append(r); + } + ChannelExec channel = clientSession.createExecChannel(sb.toString()); + try { + channel.open().await(); + } catch (InterruptedException e) { + throw (IOException) new InterruptedIOException().initCause(e); + } + + 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); + + channel.close(false); + } + + private <T> T checkNotNull(T t, String message) { + if (t == null) { + throw new IllegalStateException(String.format(message, t)); + } + return t; + } + + private String checkNotNullAndNotEmpty(String t, String message) { + t = checkNotNull(t, message).trim(); + if (t.isEmpty()) { + throw new IllegalArgumentException(String.format(message, t)); + } + return t; + } + + private <T> T[] checkNotNullAndNotEmpty(T[] t, String message) { + t = checkNotNull(t, message); + if (t.length == 0) { + throw new IllegalArgumentException(String.format(message, t)); + } + return t; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java index ccc21bc..10825b7 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java @@ -29,6 +29,7 @@ import org.apache.mina.core.session.IoSession; import org.apache.sshd.ClientChannel; import org.apache.sshd.ClientSession; import org.apache.sshd.client.ClientFactoryManager; +import org.apache.sshd.client.ScpClient; import org.apache.sshd.client.ServerKeyVerifier; import org.apache.sshd.client.UserAuth; import org.apache.sshd.client.UserInteraction; @@ -43,6 +44,7 @@ import org.apache.sshd.client.channel.ChannelSubsystem; import org.apache.sshd.client.future.AuthFuture; import org.apache.sshd.client.future.DefaultAuthFuture; import org.apache.sshd.client.future.OpenFuture; +import org.apache.sshd.client.scp.DefaultScpClient; import org.apache.sshd.common.Channel; import org.apache.sshd.common.KeyExchange; import org.apache.sshd.common.KeyPairProvider; @@ -250,6 +252,10 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession return channel; } + public ScpClient createScpClient() { + return new DefaultScpClient(this); + } + public SshdSocketAddress startLocalPortForwarding(SshdSocketAddress local, SshdSocketAddress remote) throws IOException { return getTcpipForwarder().startLocalPortForwarding(local, remote); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java index 84886a6..28f5f4a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java @@ -175,8 +175,8 @@ public interface FactoryManager { /** * Retrieve the <code>FileSystemFactory</code> to be used to traverse the file system. * - * @return a valid <code>FileSystemFactory</code> object or <code>null</code> if commands - * are not supported on this server + * @return a valid <code>FileSystemFactory</code> object or <code>null</code> if file based + * interactions are not supported on this server */ FileSystemFactory getFileSystemFactory(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/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 new file mode 100644 index 0000000..17b55c1 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java @@ -0,0 +1,363 @@ +/* + * 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.common.scp; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.apache.sshd.common.SshException; +import org.apache.sshd.common.file.FileSystemView; +import org.apache.sshd.common.file.SshFile; +import org.apache.sshd.common.util.DirectoryScanner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class ScpHelper { + + protected static final Logger log = LoggerFactory.getLogger(ScpHelper.class); + + public static final int OK = 0; + public static final int WARNING = 1; + public static final int ERROR = 2; + + protected final FileSystemView root; + protected final InputStream in; + protected final OutputStream out; + + public ScpHelper(InputStream in, OutputStream out, FileSystemView root) { + this.in = in; + this.out = out; + this.root = root; + } + + public void receive(SshFile path, boolean recursive, boolean shouldBeDir) throws IOException { + if (shouldBeDir) { + if (!path.doesExist()) { + throw new SshException("Target directory " + path.toString() + " does not exists"); + } + if (!path.isDirectory()) { + throw new SshException("Target directory " + path.toString() + " is not a directory"); + } + } + ack(); + for (;;) + { + String line; + boolean isDir = false; + int c = readAck(true); + switch (c) + { + case -1: + return; + case 'D': + isDir = true; + case 'C': + line = ((char) c) + readLine(); + break; + case 'T': + readLine(); + ack(); + continue; + case 'E': + readLine(); + return; + default: + //a real ack that has been acted upon already + continue; + } + + if (recursive && isDir) + { + receiveDir(line, path); + } + else + { + receiveFile(line, path); + } + } + } + + + public void receiveDir(String header, SshFile path) throws IOException { + if (log.isDebugEnabled()) { + log.debug("Writing dir {}", path); + } + if (!header.startsWith("D")) { + throw new IOException("Expected a D message but got '" + header + "'"); + } + + String perms = header.substring(1, 5); + int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6))); + String name = header.substring(header.indexOf(' ', 6) + 1); + + if (length != 0) { + throw new IOException("Expected 0 length for directory but got " + length); + } + SshFile file; + if (path.doesExist() && path.isDirectory()) { + file = root.getFile(path, name); + } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) { + file = path; + } else { + throw new IOException("Can not write to " + path); + } + if (!(file.doesExist() && file.isDirectory()) && !file.mkdir()) { + throw new IOException("Could not create directory " + file); + } + + ack(); + + for (;;) { + header = readLine(); + if (header.startsWith("C")) { + receiveFile(header, file); + } else if (header.startsWith("D")) { + receiveDir(header, file); + } else if (header.equals("E")) { + ack(); + break; + } else if (header.equals("T")) { + ack(); + break; + } else { + throw new IOException("Unexpected message: '" + header + "'"); + } + } + + } + + public void receiveFile(String header, SshFile path) throws IOException { + if (log.isDebugEnabled()) { + log.debug("Writing file {}", path); + } + if (!header.startsWith("C")) { + throw new IOException("Expected a C message but got '" + header + "'"); + } + + String perms = header.substring(1, 5); + long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6))); + String name = header.substring(header.indexOf(' ', 6) + 1); + + SshFile file; + if (path.doesExist() && path.isDirectory()) { + file = root.getFile(path, name); + } else if (path.doesExist() && path.isFile()) { + file = path; + } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) { + file = path; + } else { + throw new IOException("Can not write to " + path); + } + if (file.doesExist() && file.isDirectory()) { + throw new IOException("File is a directory: " + file); + } else if (file.doesExist() && !file.isWritable()) { + throw new IOException("Can not write to file: " + file); + } + OutputStream os = file.createOutputStream(0); + try { + ack(); + + byte[] buffer = new byte[8192]; + while (length > 0) { + int len = (int) Math.min(length, buffer.length); + len = in.read(buffer, 0, len); + if (len <= 0) { + throw new IOException("End of stream reached"); + } + os.write(buffer, 0, len); + length -= len; + } + } finally { + os.close(); + } + + ack(); + readAck(false); + } + + public String readLine() throws IOException { + return readLine(false); + } + + public String readLine(boolean canEof) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (;;) { + int c = in.read(); + if (c == '\n') { + return baos.toString(); + } else if (c == -1) { + if (!canEof) { + throw new EOFException(); + } + return null; + } else { + baos.write(c); + } + } + } + + public void send(List<String> paths, boolean recursive) throws IOException { + for (String pattern : paths) { + int idx = pattern.indexOf('*'); + if (idx >= 0) { + String basedir = ""; + int lastSep = pattern.substring(0, idx).lastIndexOf('/'); + if (lastSep >= 0) { + basedir = pattern.substring(0, lastSep); + pattern = pattern.substring(lastSep + 1); + } + String[] included = new DirectoryScanner(basedir, pattern).scan(); + for (String path : included) { + SshFile file = root.getFile(basedir + "/" + path); + if (file.isFile()) { + sendFile(file); + } else if (file.isDirectory()) { + if (!recursive) { + out.write(ScpHelper.WARNING); + out.write((path + " not a regular file\n").getBytes()); + } else { + sendDir(file); + } + } else { + out.write(ScpHelper.WARNING); + out.write((path + " unknown file type\n").getBytes()); + } + } + } else { + String basedir = ""; + int lastSep = pattern.lastIndexOf('/'); + if (lastSep >= 0) { + basedir = pattern.substring(0, lastSep); + pattern = pattern.substring(lastSep + 1); + } + SshFile file = root.getFile(basedir + "/" + pattern); + if (!file.doesExist()) { + throw new IOException(file + ": no such file or directory"); + } + if (file.isFile()) { + sendFile(file); + } else if (file.isDirectory()) { + if (!recursive) { + throw new IOException(file + " not a regular file"); + } else { + sendDir(file); + } + } else { + throw new IOException(file + ": unknown file type"); + } + } + } + } + + public void sendFile(SshFile path) throws IOException { + if (log.isDebugEnabled()) { + log.debug("Reading file {}", path); + } + StringBuffer buf = new StringBuffer(); + buf.append("C"); + buf.append("0644"); // TODO: what about perms + buf.append(" "); + buf.append(path.getSize()); // length + buf.append(" "); + buf.append(path.getName()); + buf.append("\n"); + out.write(buf.toString().getBytes()); + out.flush(); + readAck(false); + + InputStream is = path.createInputStream(0); + try { + byte[] buffer = new byte[8192]; + for (;;) { + int len = is.read(buffer, 0, buffer.length); + if (len == -1) { + break; + } + out.write(buffer, 0, len); + } + } finally { + is.close(); + } + ack(); + readAck(false); + } + + public void sendDir(SshFile path) throws IOException { + if (log.isDebugEnabled()) { + log.debug("Reading directory {}", path); + } + StringBuffer buf = new StringBuffer(); + buf.append("D"); + buf.append("0755"); // what about perms + buf.append(" "); + buf.append("0"); // length + buf.append(" "); + buf.append(path.getName()); + buf.append("\n"); + out.write(buf.toString().getBytes()); + out.flush(); + readAck(false); + + for (SshFile child : path.listSshFiles()) { + if (child.isFile()) { + sendFile(child); + } else if (child.isDirectory()) { + sendDir(child); + } + } + + out.write("E\n".getBytes()); + out.flush(); + readAck(false); + } + + public void ack() throws IOException { + out.write(0); + out.flush(); + } + + public int readAck(boolean canEof) throws IOException { + int c = in.read(); + switch (c) { + case -1: + if (!canEof) { + throw new EOFException(); + } + break; + case OK: + break; + case WARNING: + log.warn("Received warning: " + readLine()); + break; + case ERROR: + throw new IOException("Received nack: " + readLine()); + default: + break; + } + return c; + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/16fc6a80/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 950d569..8ecd7cd 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 @@ -18,20 +18,19 @@ */ package org.apache.sshd.server.command; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; -import org.apache.sshd.common.util.DirectoryScanner; +import org.apache.sshd.common.file.FileSystemAware; +import org.apache.sshd.common.file.FileSystemView; +import org.apache.sshd.common.scp.ScpHelper; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; import org.apache.sshd.server.ExitCallback; -import org.apache.sshd.common.file.FileSystemAware; -import org.apache.sshd.common.file.FileSystemView; -import org.apache.sshd.common.file.SshFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,19 +44,15 @@ import org.slf4j.LoggerFactory; public class ScpCommand implements Command, Runnable, FileSystemAware { protected static final Logger log = LoggerFactory.getLogger(ScpCommand.class); - protected static final int OK = 0; - protected static final int WARNING = 1; - protected static final int ERROR = 2; protected String name; protected boolean optR; protected boolean optT; protected boolean optF; - protected boolean optV; protected boolean optD; - protected boolean optP; + protected boolean optP; // TODO: handle modification times protected FileSystemView root; - protected String path; + protected List<String> paths; protected InputStream in; protected OutputStream out; protected OutputStream err; @@ -69,7 +64,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { if (log.isDebugEnabled()) { log.debug("Executing command {}", name); } - path = "."; + paths = new ArrayList<String>(); for (int i = 1; i < args.length; i++) { if (args[i].charAt(0) == '-') { for (int j = 1; j < args[i].length(); j++) { @@ -86,9 +81,6 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { case 't': optT = true; break; - case 'v': - optV = true; - break; case 'd': optD = true; break; @@ -98,12 +90,15 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { } } } else if (i == args.length - 1) { - path = args[args.length - 1]; + paths.add(args[args.length - 1]); } } 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) { @@ -122,6 +117,10 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { this.callback = callback; } + public void setFileSystemView(FileSystemView view) { + this.root = view; + } + public void start(Environment env) throws IOException { if (error != null) { throw error; @@ -133,104 +132,20 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { } public void run() { - int exitValue = OK; + int exitValue = ScpHelper.OK; String exitMessage = null; - + ScpHelper helper = new ScpHelper(in, out, root); try { - if (optT) - { - ack(); - for (; ;) - { - String line; - boolean isDir = false; - int c = readAck(true); - switch (c) - { - case -1: - return; - case 'D': - isDir = true; - case 'C': - line = ((char) c) + readLine(); - break; - case 'T': - readLine(); - ack(); - continue; - case 'E': - readLine(); - return; - default: - //a real ack that has been acted upon already - continue; - } - - if (optR && isDir) - { - writeDir(line, root.getFile(path)); - } - else - { - writeFile(line, root.getFile(path)); - } - } + if (optT) { + helper.receive(root.getFile(paths.get(0)), optR, optD); } else if (optF) { - String pattern = path; - int idx = pattern.indexOf('*'); - if (idx >= 0) { - String basedir = ""; - int lastSep = pattern.substring(0, idx).lastIndexOf('/'); - if (lastSep >= 0) { - basedir = pattern.substring(0, lastSep); - pattern = pattern.substring(lastSep + 1); - } - String[] included = new DirectoryScanner(basedir, pattern).scan(); - for (String path : included) { - SshFile file = root.getFile(basedir + "/" + path); - if (file.isFile()) { - readFile(file); - } else if (file.isDirectory()) { - if (!optR) { - out.write(WARNING); - out.write((path + " not a regular file\n").getBytes()); - } else { - readDir(file); - } - } else { - out.write(WARNING); - out.write((path + " unknown file type\n").getBytes()); - } - } - } else { - String basedir = ""; - int lastSep = pattern.lastIndexOf('/'); - if (lastSep >= 0) { - basedir = pattern.substring(0, lastSep); - pattern = pattern.substring(lastSep + 1); - } - SshFile file = root.getFile(basedir + "/" + pattern); - if (!file.doesExist()) { - throw new IOException(file + ": no such file or directory"); - } - if (file.isFile()) { - readFile(file); - } else if (file.isDirectory()) { - if (!optR) { - throw new IOException(file + " not a regular file"); - } else { - readDir(file); - } - } else { - throw new IOException(file + ": unknown file type"); - } - } + helper.send(paths, optR); } else { throw new IOException("Unsupported mode"); } } catch (IOException e) { try { - exitValue = ERROR; + exitValue = ScpHelper.ERROR; exitMessage = e.getMessage() == null ? "" : e.getMessage(); out.write(exitValue); out.write(exitMessage.getBytes()); @@ -247,207 +162,5 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { } } - protected void writeDir(String header, SshFile path) throws IOException { - if (log.isDebugEnabled()) { - log.debug("Writing dir {}", path); - } - if (!header.startsWith("D")) { - throw new IOException("Expected a D message but got '" + header + "'"); - } - - String perms = header.substring(1, 5); - int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6))); - String name = header.substring(header.indexOf(' ', 6) + 1); - - if (length != 0) { - throw new IOException("Expected 0 length for directory but got " + length); - } - SshFile file; - if (path.doesExist() && path.isDirectory()) { - file = root.getFile(path, name); - } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) { - file = path; - } else { - throw new IOException("Can not write to " + path); - } - if (!(file.doesExist() && file.isDirectory()) && !file.mkdir()) { - throw new IOException("Could not create directory " + file); - } - - ack(); - - for (;;) { - header = readLine(); - if (header.startsWith("C")) { - writeFile(header, file); - } else if (header.startsWith("D")) { - writeDir(header, file); - } else if (header.equals("E")) { - ack(); - break; - } else if (header.equals("T")) { - ack(); - break; - } else { - throw new IOException("Unexpected message: '" + header + "'"); - } - } - - } - - protected void writeFile(String header, SshFile path) throws IOException { - if (log.isDebugEnabled()) { - log.debug("Writing file {}", path); - } - if (!header.startsWith("C")) { - throw new IOException("Expected a C message but got '" + header + "'"); - } - - String perms = header.substring(1, 5); - long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6))); - String name = header.substring(header.indexOf(' ', 6) + 1); - - SshFile file; - if (path.doesExist() && path.isDirectory()) { - file = root.getFile(path, name); - } else if (path.doesExist() && path.isFile()) { - file = path; - } else if (!path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory()) { - file = path; - } else { - throw new IOException("Can not write to " + path); - } - if (file.doesExist() && file.isDirectory()) { - throw new IOException("File is a directory: " + file); - } else if (file.doesExist() && !file.isWritable()) { - throw new IOException("Can not write to file: " + file); - } - OutputStream os = file.createOutputStream(0); - try { - ack(); - - byte[] buffer = new byte[8192]; - while (length > 0) { - int len = (int) Math.min(length, buffer.length); - len = in.read(buffer, 0, len); - if (len <= 0) { - throw new IOException("End of stream reached"); - } - os.write(buffer, 0, len); - length -= len; - } - } finally { - os.close(); - } - - ack(); - readAck(false); - } - - protected String readLine() throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (;;) { - int c = in.read(); - if (c == '\n') { - return baos.toString(); - } else if (c == -1) { - throw new IOException("End of stream"); - } else { - baos.write(c); - } - } - } - - protected void readFile(SshFile path) throws IOException { - if (log.isDebugEnabled()) { - log.debug("Reading file {}", path); - } - StringBuffer buf = new StringBuffer(); - buf.append("C"); - buf.append("0644"); // what about perms - buf.append(" "); - buf.append(path.getSize()); // length - buf.append(" "); - buf.append(path.getName()); - buf.append("\n"); - out.write(buf.toString().getBytes()); - out.flush(); - readAck(false); - - InputStream is = path.createInputStream(0); - try { - byte[] buffer = new byte[8192]; - for (;;) { - int len = is.read(buffer, 0, buffer.length); - if (len == -1) { - break; - } - out.write(buffer, 0, len); - } - } finally { - is.close(); - } - ack(); - readAck(false); - } - - protected void readDir(SshFile path) throws IOException { - if (log.isDebugEnabled()) { - log.debug("Reading directory {}", path); - } - StringBuffer buf = new StringBuffer(); - buf.append("D"); - buf.append("0755"); // what about perms - buf.append(" "); - buf.append("0"); // length - buf.append(" "); - buf.append(path.getName()); - buf.append("\n"); - out.write(buf.toString().getBytes()); - out.flush(); - readAck(false); - - for (SshFile child : path.listSshFiles()) { - if (child.isFile()) { - readFile(child); - } else if (child.isDirectory()) { - readDir(child); - } - } - - out.write("E\n".getBytes()); - out.flush(); - readAck(false); - } - - protected void ack() throws IOException { - out.write(0); - out.flush(); - } - - protected int readAck(boolean canEof) throws IOException { - int c = in.read(); - switch (c) { - case -1: - if (!canEof) { - throw new EOFException(); - } - break; - case OK: - break; - case WARNING: - log.warn("Received warning: " + readLine()); - break; - case ERROR: - throw new IOException("Received nack: " + readLine()); - default: - break; - } - return c; - } - - public void setFileSystemView(FileSystemView view) { - this.root = view; - } }
