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 2018/04/25 05:01:23 UTC
[2/8] mina-sshd git commit: [SSHD-818] Split SCP code (client +
server) to its own module
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/af415e5f/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java b/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
new file mode 100644
index 0000000..99e3e34
--- /dev/null
+++ b/sshd-scp/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
@@ -0,0 +1,272 @@
+/*
+ * 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.server.scp;
+
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpFileOpenerHolder;
+import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.util.EventListenerUtils;
+import org.apache.sshd.common.util.ObjectBuilder;
+import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+
+/**
+ * This <code>CommandFactory</code> can be used as a standalone command factory
+ * or can be used to augment another <code>CommandFactory</code> and provides
+ * <code>SCP</code> support.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see ScpCommand
+ */
+public class ScpCommandFactory
+ implements ScpFileOpenerHolder,
+ CommandFactory,
+ Cloneable,
+ ExecutorServiceConfigurer {
+ /**
+ * A useful {@link ObjectBuilder} for {@link ScpCommandFactory}
+ */
+ public static class Builder implements ObjectBuilder<ScpCommandFactory> {
+ private final ScpCommandFactory factory = new ScpCommandFactory();
+
+ public Builder() {
+ super();
+ }
+
+ public Builder withFileOpener(ScpFileOpener opener) {
+ factory.setScpFileOpener(opener);
+ return this;
+ }
+
+ public Builder withDelegate(CommandFactory delegate) {
+ factory.setDelegateCommandFactory(delegate);
+ return this;
+ }
+
+ public Builder withExecutorService(ExecutorService service) {
+ factory.setExecutorService(service);
+ return this;
+ }
+
+ public Builder withShutdownOnExit(boolean shutdown) {
+ factory.setShutdownOnExit(shutdown);
+ return this;
+ }
+
+ public Builder withSendBufferSize(int sendSize) {
+ factory.setSendBufferSize(sendSize);
+ return this;
+ }
+
+ public Builder withReceiveBufferSize(int receiveSize) {
+ factory.setReceiveBufferSize(receiveSize);
+ return this;
+ }
+
+ public Builder addEventListener(ScpTransferEventListener listener) {
+ factory.addEventListener(listener);
+ return this;
+ }
+
+ public Builder removeEventListener(ScpTransferEventListener listener) {
+ factory.removeEventListener(listener);
+ return this;
+ }
+
+ @Override
+ public ScpCommandFactory build() {
+ return factory.clone();
+ }
+ }
+
+ /*
+ * NOTE: we expose setters since there is no problem to change these settings between
+ * successive invocations of the 'createCommand' method
+ */
+ private CommandFactory delegate;
+ private ExecutorService executors;
+ private boolean shutdownExecutor;
+ private ScpFileOpener fileOpener;
+ private int sendBufferSize = ScpHelper.MIN_SEND_BUFFER_SIZE;
+ private int receiveBufferSize = ScpHelper.MIN_RECEIVE_BUFFER_SIZE;
+ private Collection<ScpTransferEventListener> listeners = new CopyOnWriteArraySet<>();
+ private ScpTransferEventListener listenerProxy;
+
+ public ScpCommandFactory() {
+ listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), listeners);
+ }
+
+ @Override
+ public ScpFileOpener getScpFileOpener() {
+ return fileOpener;
+ }
+
+ @Override
+ public void setScpFileOpener(ScpFileOpener fileOpener) {
+ this.fileOpener = fileOpener;
+ }
+
+ public CommandFactory getDelegateCommandFactory() {
+ return delegate;
+ }
+
+ /**
+ * @param factory A {@link CommandFactory} to be used if the
+ * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
+ * will be thrown when attempting to invoke {@link #createCommand(String)}
+ * with a non-SCP command
+ */
+ public void setDelegateCommandFactory(CommandFactory factory) {
+ delegate = factory;
+ }
+
+ @Override
+ public ExecutorService getExecutorService() {
+ return executors;
+ }
+
+ /**
+ * @param service An {@link ExecutorService} to be used when
+ * starting {@link ScpCommand} execution. If {@code null} then a single-threaded
+ * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
+ * when the command is terminated - unless it is the ad-hoc service, which will be
+ * shutdown regardless
+ */
+ @Override
+ public void setExecutorService(ExecutorService service) {
+ executors = service;
+ }
+
+ @Override
+ public boolean isShutdownOnExit() {
+ return shutdownExecutor;
+ }
+
+ @Override
+ public void setShutdownOnExit(boolean shutdown) {
+ shutdownExecutor = shutdown;
+ }
+
+ public int getSendBufferSize() {
+ return sendBufferSize;
+ }
+
+ /**
+ * @param sendSize Size (in bytes) of buffer to use when sending files
+ * @see ScpHelper#MIN_SEND_BUFFER_SIZE
+ */
+ public void setSendBufferSize(int sendSize) {
+ if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
+ throw new IllegalArgumentException("<ScpCommandFactory>() send buffer size "
+ + "(" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")");
+ }
+ sendBufferSize = sendSize;
+ }
+
+ public int getReceiveBufferSize() {
+ return receiveBufferSize;
+ }
+
+ /**
+ * @param receiveSize Size (in bytes) of buffer to use when receiving files
+ * @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE
+ */
+ public void setReceiveBufferSize(int receiveSize) {
+ if (receiveSize < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) {
+ throw new IllegalArgumentException("<ScpCommandFactory>() receive buffer size "
+ + "(" + receiveSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")");
+ }
+ receiveBufferSize = receiveSize;
+ }
+
+ /**
+ * @param listener The {@link ScpTransferEventListener} to add
+ * @return {@code true} if this is a <U>new</U> listener instance,
+ * {@code false} if the listener is already registered
+ * @throws IllegalArgumentException if {@code null} listener
+ */
+ public boolean addEventListener(ScpTransferEventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("No listener instance");
+ }
+
+ return listeners.add(listener);
+ }
+
+ /**
+ * @param listener The {@link ScpTransferEventListener} to remove
+ * @return {@code true} if the listener was registered and removed,
+ * {@code false} if the listener was not registered to begin with
+ * @throws IllegalArgumentException if {@code null} listener
+ */
+ public boolean removeEventListener(ScpTransferEventListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("No listener instance");
+ }
+
+ return listeners.remove(listener);
+ }
+
+ /**
+ * Parses a command string and verifies that the basic syntax is
+ * correct. If parsing fails the responsibility is delegated to
+ * the configured {@link CommandFactory} instance; if one exist.
+ *
+ * @param command command to parse
+ * @return configured {@link Command} instance
+ * @throws IllegalArgumentException if not an SCP command and no
+ * delegate command factory is available
+ * @see ScpHelper#SCP_COMMAND_PREFIX
+ */
+ @Override
+ public Command createCommand(String command) {
+ if (command.startsWith(ScpHelper.SCP_COMMAND_PREFIX)) {
+ return new ScpCommand(command,
+ getExecutorService(), isShutdownOnExit(),
+ getSendBufferSize(), getReceiveBufferSize(),
+ getScpFileOpener(), listenerProxy);
+ }
+
+ CommandFactory factory = getDelegateCommandFactory();
+ if (factory != null) {
+ return factory.createCommand(command);
+ }
+
+ throw new IllegalArgumentException("Unknown command, does not begin with '" + ScpHelper.SCP_COMMAND_PREFIX + "': " + command);
+ }
+
+ @Override
+ public ScpCommandFactory clone() {
+ try {
+ ScpCommandFactory other = getClass().cast(super.clone());
+ // clone the listeners set as well
+ other.listeners = new CopyOnWriteArraySet<>(this.listeners);
+ other.listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), other.listeners);
+ return other;
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e); // un-expected...
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/af415e5f/sshd-scp/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-scp/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-scp/src/test/java/org/apache/sshd/client/scp/ScpTest.java
new file mode 100644
index 0000000..7b7d2e8
--- /dev/null
+++ b/sshd-scp/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -0,0 +1,1203 @@
+/*
+ * 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.scp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.scp.ScpException;
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.scp.ScpCommand;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.JSchLogger;
+import org.apache.sshd.util.test.SimpleUserInfo;
+import org.apache.sshd.util.test.Utils;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.ConnectionInfo;
+import ch.ethz.ssh2.SCPClient;
+
+/**
+ * Test for SCP support.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ScpTest extends BaseTestSupport {
+ private static final ScpTransferEventListener DEBUG_LISTENER = new ScpTransferEventListener() {
+ @Override
+ public void startFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms) {
+ logEvent("starFolderEvent", op, file, false, -1L, perms, null);
+ }
+
+ @Override
+ public void startFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms) {
+ logEvent("startFileEvent", op, file, true, length, perms, null);
+
+ }
+
+ @Override
+ public void endFolderEvent(FileOperation op, Path file, Set<PosixFilePermission> perms, Throwable thrown) {
+ logEvent("endFolderEvent", op, file, false, -1L, perms, thrown);
+ }
+
+ @Override
+ public void endFileEvent(FileOperation op, Path file, long length, Set<PosixFilePermission> perms, Throwable thrown) {
+ logEvent("endFileEvent", op, file, true, length, perms, thrown);
+ }
+
+ private void logEvent(String type, FileOperation op, Path path, boolean isFile, long length, Collection<PosixFilePermission> perms, Throwable t) {
+ if (!OUTPUT_DEBUG_MESSAGES) {
+ return; // just in case
+ }
+ StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
+ sb.append('\t').append(type)
+ .append('[').append(op).append(']')
+ .append(' ').append(isFile ? "File" : "Directory").append('=').append(path)
+ .append(' ').append("length=").append(length)
+ .append(' ').append("perms=").append(perms);
+ if (t != null) {
+ sb.append(' ').append("ERROR=").append(t.getClass().getSimpleName()).append(": ").append(t.getMessage());
+ }
+ outputDebugMessage(sb.toString());
+ }
+ };
+
+ private static SshServer sshd;
+ private static int port;
+ private static SshClient client;
+ private final FileSystemFactory fileSystemFactory;
+
+ public ScpTest() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @BeforeClass
+ public static void setupClientAndServer() throws Exception {
+ JSchLogger.init();
+ sshd = Utils.setupTestServer(ScpTest.class);
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.start();
+ port = sshd.getPort();
+
+ client = Utils.setupTestClient(ScpTest.class);
+ client.start();
+ }
+
+ @AfterClass
+ public static void tearDownClientAndServer() throws Exception {
+ if (sshd != null) {
+ try {
+ sshd.stop(true);
+ } finally {
+ sshd = null;
+ }
+ }
+
+ if (client != null) {
+ try {
+ client.stop();
+ } finally {
+ client = null;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd.setFileSystemFactory(fileSystemFactory);
+ }
+
+ @Test
+ public void testNormalizedScpRemotePaths() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localFile = localDir.resolve("file.txt");
+ byte[] data = Utils.writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
+ String localPath = localFile.toString();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ String[] remoteComps = GenericUtils.split(remotePath, '/');
+ Factory<? extends Random> factory = client.getRandomFactory();
+ Random rnd = factory.create();
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ StringBuilder sb = new StringBuilder(remotePath.length() + Long.SIZE);
+ for (int i = 0; i < Math.max(Long.SIZE, remoteComps.length); i++) {
+ if (sb.length() > 0) {
+ sb.setLength(0); // start again
+ }
+
+ sb.append(remoteComps[0]);
+ for (int j = 1; j < remoteComps.length; j++) {
+ String name = remoteComps[j];
+ slashify(sb, rnd);
+ sb.append(name);
+ }
+ slashify(sb, rnd);
+
+ String path = sb.toString();
+ scp.upload(localPath, path);
+ assertTrue("Remote file not ready for " + path, waitForFile(remoteFile, data.length, TimeUnit.SECONDS.toMillis(5L)));
+
+ byte[] actual = Files.readAllBytes(remoteFile);
+ assertArrayEquals("Mismatched uploaded data for " + path, data, actual);
+ Files.delete(remoteFile);
+ assertFalse("Remote file (" + remoteFile + ") not deleted for " + path, Files.exists(remoteFile));
+ }
+ }
+ }
+
+ private static int slashify(StringBuilder sb, Random rnd) {
+ int slashes = 1 /* at least one slash */ + rnd.random(Byte.SIZE);
+ for (int k = 0; k < slashes; k++) {
+ sb.append('/');
+ }
+
+ return slashes;
+ }
+
+ @Test
+ public void testUploadAbsoluteDriveLetter() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localFile = localDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(localFile, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve(localFile.getFileName().toString());
+ String localPath = localFile.toString();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ scp.upload(localPath, remotePath);
+ assertFileLength(remoteFile, data.length, TimeUnit.SECONDS.toMillis(5L));
+
+ Path secondRemote = remoteDir.resolve("file-2.txt");
+ String secondPath = Utils.resolveRelativeRemotePath(parentPath, secondRemote);
+ scp.upload(localPath, secondPath);
+ assertFileLength(secondRemote, data.length, TimeUnit.SECONDS.toMillis(5L));
+
+ Path pathRemote = remoteDir.resolve("file-path.txt");
+ String pathPath = Utils.resolveRelativeRemotePath(parentPath, pathRemote);
+ scp.upload(localFile, pathPath);
+ assertFileLength(pathRemote, data.length, TimeUnit.SECONDS.toMillis(5L));
+ }
+ }
+
+ @Test
+ public void testScpUploadOverwrite() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
+
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localFile = localDir.resolve("file.txt");
+ Utils.writeFile(localFile, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve(localFile.getFileName());
+ Utils.writeFile(remoteFile, data + data);
+
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ scp.upload(localFile.toString(), remotePath);
+ assertFileLength(remoteFile, data.length(), TimeUnit.SECONDS.toMillis(5L));
+ }
+ }
+
+ @Test
+ public void testScpUploadZeroLengthFile() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path zeroLocal = localDir.resolve("zero.txt");
+
+ try (FileChannel fch = FileChannel.open(zeroLocal, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ if (fch.size() > 0L) {
+ fch.truncate(0L);
+ }
+ }
+ assertEquals("Non-zero size for local file=" + zeroLocal, 0L, Files.size(zeroLocal));
+
+ Path zeroRemote = remoteDir.resolve(zeroLocal.getFileName());
+ if (Files.exists(zeroRemote)) {
+ Files.delete(zeroRemote);
+ }
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+ scp.upload(zeroLocal.toString(), remotePath);
+ assertFileLength(zeroRemote, 0L, TimeUnit.SECONDS.toMillis(5L));
+ }
+ }
+
+ @Test
+ public void testScpDownloadZeroLengthFile() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path zeroLocal = localDir.resolve(getCurrentTestName());
+ if (Files.exists(zeroLocal)) {
+ Files.delete(zeroLocal);
+ }
+
+ Path zeroRemote = remoteDir.resolve(zeroLocal.getFileName());
+ try (FileChannel fch = FileChannel.open(zeroRemote, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ if (fch.size() > 0L) {
+ fch.truncate(0L);
+ }
+ }
+ assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote));
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote);
+ scp.download(remotePath, zeroLocal.toString());
+ assertFileLength(zeroLocal, 0L, TimeUnit.SECONDS.toMillis(5L));
+ }
+ }
+
+ @Test
+ @Ignore("TODO investigate why this fails often")
+ public void testScpNativeOnSingleFile() throws Exception {
+ String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
+
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localOutFile = localDir.resolve("file-1.txt");
+ Path remoteDir = scpRoot.resolve("remote");
+ Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName());
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Utils.writeFile(localOutFile, data);
+
+ assertFalse("Remote folder already exists: " + remoteDir, Files.exists(remoteDir));
+
+ String localOutPath = localOutFile.toString();
+ String remoteOutPath = Utils.resolveRelativeRemotePath(parentPath, remoteOutFile);
+ outputDebugMessage("Expect upload failure %s => %s", localOutPath, remoteOutPath);
+ try {
+ scp.upload(localOutPath, remoteOutPath);
+ fail("Expected IOException for 1st time " + remoteOutPath);
+ } catch (IOException e) {
+ // ok
+ }
+
+ assertHierarchyTargetFolderExists(remoteDir);
+ outputDebugMessage("Expect upload success %s => %s", localOutPath, remoteOutPath);
+ scp.upload(localOutPath, remoteOutPath);
+ assertFileLength(remoteOutFile, data.length(), TimeUnit.SECONDS.toMillis(11L));
+
+ Path secondLocal = localDir.resolve(localOutFile.getFileName());
+ String downloadTarget = Utils.resolveRelativeRemotePath(parentPath, secondLocal);
+ outputDebugMessage("Expect download success %s => %s", remoteOutPath, downloadTarget);
+ scp.download(remoteOutPath, downloadTarget);
+ assertFileLength(secondLocal, data.length(), TimeUnit.SECONDS.toMillis(11L));
+
+ Path localPath = localDir.resolve("file-path.txt");
+ downloadTarget = Utils.resolveRelativeRemotePath(parentPath, localPath);
+ outputDebugMessage("Expect download success %s => %s", remoteOutPath, downloadTarget);
+ scp.download(remoteOutPath, downloadTarget);
+ assertFileLength(localPath, data.length(), TimeUnit.SECONDS.toMillis(11L));
+ }
+ }
+
+ @Test
+ public void testScpNativeOnMultipleFiles() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path local1 = localDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+
+ Path local2 = localDir.resolve("file-2.txt");
+ Files.write(local2, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remote1 = remoteDir.resolve(local1.getFileName());
+ String remote1Path = Utils.resolveRelativeRemotePath(parentPath, remote1);
+ String[] locals = {local1.toString(), local2.toString()};
+ try {
+ scp.upload(locals, remote1Path);
+ fail("Unexpected upload success to missing remote file: " + remote1Path);
+ } catch (IOException e) {
+ // Ok
+ }
+
+ Files.write(remote1, data);
+ try {
+ scp.upload(locals, remote1Path);
+ fail("Unexpected upload success to existing remote file: " + remote1Path);
+ } catch (IOException e) {
+ // Ok
+ }
+
+ Path remoteSubDir = assertHierarchyTargetFolderExists(remoteDir.resolve("dir"));
+ scp.upload(locals, Utils.resolveRelativeRemotePath(parentPath, remoteSubDir));
+
+ Path remoteSub1 = remoteSubDir.resolve(local1.getFileName());
+ assertFileLength(remoteSub1, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ Path remoteSub2 = remoteSubDir.resolve(local2.getFileName());
+ assertFileLength(remoteSub2, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ String[] remotes = {
+ Utils.resolveRelativeRemotePath(parentPath, remoteSub1),
+ Utils.resolveRelativeRemotePath(parentPath, remoteSub2),
+ };
+
+ try {
+ scp.download(remotes, Utils.resolveRelativeRemotePath(parentPath, local1));
+ fail("Unexpected download success to existing local file: " + local1);
+ } catch (IOException e) {
+ // Ok
+ }
+
+ Path localSubDir = localDir.resolve("dir");
+ try {
+ scp.download(remotes, localSubDir);
+ fail("Unexpected download success to non-existing folder: " + localSubDir);
+ } catch (IOException e) {
+ // Ok
+ }
+
+ assertHierarchyTargetFolderExists(localSubDir);
+ scp.download(remotes, localSubDir);
+
+ assertFileLength(localSubDir.resolve(remoteSub1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFileLength(localSubDir.resolve(remoteSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+ }
+ }
+
+ @Test
+ public void testScpNativeOnRecursiveDirs() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = scpRoot.resolve("local");
+ Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+ Path localSub1 = localSubDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(localSub1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+ Path localSub2 = localSubDir.resolve("file-2.txt");
+ Files.write(localSub2, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ scp.upload(localSubDir, Utils.resolveRelativeRemotePath(parentPath, remoteDir), ScpClient.Option.Recursive);
+
+ Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+ assertFileLength(remoteSubDir.resolve(localSub1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ Utils.deleteRecursive(localSubDir);
+
+ scp.download(Utils.resolveRelativeRemotePath(parentPath, remoteSubDir), localDir, ScpClient.Option.Recursive);
+ assertFileLength(localSub1, data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(11L));
+ }
+ }
+
+ @Test
+ public void testScpNativeOnDirWithPattern() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path local1 = localDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+ Path local2 = localDir.resolve("file-2.txt");
+ Files.write(local2, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+ scp.upload(localDir.toString() + File.separator + "*", remotePath);
+ assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFileLength(remoteDir.resolve(local2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ Files.delete(local1);
+ Files.delete(local2);
+ scp.download(remotePath + "/*", localDir);
+ assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFileLength(local2, data.length, TimeUnit.SECONDS.toMillis(11L));
+ }
+ }
+
+ @Test
+ public void testScpNativeOnMixedDirAndFiles() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = scpRoot.resolve("local");
+ Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+ Path local1 = localDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+ Path localSub2 = localSubDir.resolve("file-2.txt");
+ Files.write(localSub2, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+ scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive);
+ assertFileLength(remoteDir.resolve(local1.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+ assertFileLength(remoteSubDir.resolve(localSub2.getFileName()), data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ Files.delete(local1);
+ Utils.deleteRecursive(localSubDir);
+
+ scp.download(remotePath + "/*", localDir);
+ assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertFalse("Unexpected recursive local file: " + localSub2, Files.exists(localSub2));
+
+ Files.delete(local1);
+ scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive);
+ assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(5L));
+ assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(5L));
+ }
+ }
+
+ @Test
+ public void testScpNativePreserveAttributes() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = scpRoot.resolve("local");
+ Path localSubDir = assertHierarchyTargetFolderExists(localDir.resolve("dir"));
+ // convert everything to seconds since this is the SCP timestamps granularity
+ final long lastModMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
+ final long lastModSecs = TimeUnit.MILLISECONDS.toSeconds(lastModMillis);
+ Path local1 = localDir.resolve("file-1.txt");
+ byte[] data = Utils.writeFile(local1, getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL);
+
+ File lclFile1 = local1.toFile();
+ boolean lcl1ModSet = lclFile1.setLastModified(lastModMillis);
+ lclFile1.setExecutable(true, true);
+ lclFile1.setWritable(false, false);
+
+ Path localSub2 = localSubDir.resolve("file-2.txt");
+ Files.write(localSub2, data);
+ File lclSubFile2 = localSub2.toFile();
+ boolean lclSub2ModSet = lclSubFile2.setLastModified(lastModMillis);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+ scp.upload(localDir.toString() + File.separator + "*", remotePath, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+
+ Path remote1 = remoteDir.resolve(local1.getFileName());
+ assertFileLength(remote1, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ File remFile1 = remote1.toFile();
+ assertLastModifiedTimeEquals(remFile1, lcl1ModSet, lastModSecs);
+
+ Path remoteSubDir = remoteDir.resolve(localSubDir.getFileName());
+ Path remoteSub2 = remoteSubDir.resolve(localSub2.getFileName());
+ assertFileLength(remoteSub2, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ File remSubFile2 = remoteSub2.toFile();
+ assertLastModifiedTimeEquals(remSubFile2, lclSub2ModSet, lastModSecs);
+
+ Utils.deleteRecursive(localDir);
+ assertHierarchyTargetFolderExists(localDir);
+
+ scp.download(remotePath + "/*", localDir, ScpClient.Option.Recursive, ScpClient.Option.PreserveAttributes);
+ assertFileLength(local1, data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertLastModifiedTimeEquals(lclFile1, lcl1ModSet, lastModSecs);
+ assertFileLength(localSub2, data.length, TimeUnit.SECONDS.toMillis(11L));
+ assertLastModifiedTimeEquals(lclSubFile2, lclSub2ModSet, lastModSecs);
+ }
+ }
+
+ @Test
+ public void testStreamsUploadAndDownload() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClient scp = createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve("file.txt");
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ outputDebugMessage("Upload data to %s", remotePath);
+ scp.upload(data, remotePath, EnumSet.allOf(PosixFilePermission.class), null);
+ assertFileLength(remoteFile, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ byte[] uploaded = Files.readAllBytes(remoteFile);
+ assertArrayEquals("Mismatched uploaded data", data, uploaded);
+
+ outputDebugMessage("Download data from %s", remotePath);
+ byte[] downloaded = scp.downloadBytes(remotePath);
+ assertArrayEquals("Mismatched downloaded data", uploaded, downloaded);
+ }
+ }
+
+ @Test // see SSHD-649
+ public void testScpFileOpener() throws Exception {
+ class TrackingFileOpener extends DefaultScpFileOpener {
+ private final AtomicInteger readCount = new AtomicInteger(0);
+ private final AtomicInteger writeCount = new AtomicInteger(0);
+
+ TrackingFileOpener() {
+ super();
+ }
+
+ public AtomicInteger getReadCount() {
+ return readCount;
+ }
+
+ public AtomicInteger getWriteCount() {
+ return writeCount;
+ }
+
+ @Override
+ public InputStream openRead(Session session, Path file, OpenOption... options) throws IOException {
+ int count = readCount.incrementAndGet();
+ outputDebugMessage("openRead(%s)[%s] count=%d", session, file, count);
+ return super.openRead(session, file, options);
+ }
+
+ @Override
+ public OutputStream openWrite(Session session, Path file, OpenOption... options) throws IOException {
+ int count = writeCount.incrementAndGet();
+ outputDebugMessage("openWrite(%s)[%s] count=%d", session, file, count);
+ return super.openWrite(session, file, options);
+ }
+ }
+
+ ScpCommandFactory factory = (ScpCommandFactory) sshd.getCommandFactory();
+ ScpFileOpener opener = factory.getScpFileOpener();
+ TrackingFileOpener serverOpener = new TrackingFileOpener();
+ factory.setScpFileOpener(serverOpener);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ TrackingFileOpener clientOpener = new TrackingFileOpener();
+ ScpClientCreator creator = ScpClientCreator.instance();
+ ScpClient scp = creator.createScpClient(session, clientOpener);
+
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot);
+ Path localFile = remoteDir.resolve("data.txt");
+ byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ Files.write(localFile, data);
+
+ Path remoteFile = remoteDir.resolve("upload.txt");
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ outputDebugMessage("Upload data to %s", remotePath);
+ scp.upload(localFile, remotePath);
+ assertFileLength(remoteFile, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ AtomicInteger serverRead = serverOpener.getReadCount();
+ assertEquals("Mismatched server upload open read count", 0, serverRead.get());
+
+ AtomicInteger serverWrite = serverOpener.getWriteCount();
+ assertEquals("Mismatched server upload write count", 1, serverWrite.getAndSet(0));
+
+ AtomicInteger clientRead = clientOpener.getReadCount();
+ assertEquals("Mismatched client upload read count", 1, clientRead.getAndSet(0));
+
+ AtomicInteger clientWrite = clientOpener.getWriteCount();
+ assertEquals("Mismatched client upload write count", 0, clientWrite.get());
+
+ Files.delete(localFile);
+ scp.download(remotePath, localFile);
+ assertFileLength(localFile, data.length, TimeUnit.SECONDS.toMillis(11L));
+
+ assertEquals("Mismatched server download open read count", 1, serverRead.getAndSet(0));
+ assertEquals("Mismatched server download write count", 0, serverWrite.get());
+ assertEquals("Mismatched client download read count", 0, clientRead.get());
+ assertEquals("Mismatched client download write count", 1, clientWrite.getAndSet(0));
+ } finally {
+ factory.setScpFileOpener(opener);
+ }
+ }
+
+ @Test // see SSHD-628
+ public void testScpExitStatusPropagation() throws Exception {
+ final int testExitValue = 7365;
+ class InternalScpCommand extends ScpCommand implements ExitCallback {
+ private ExitCallback delegate;
+
+ InternalScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit,
+ int sendSize, int receiveSize, ScpFileOpener opener, ScpTransferEventListener eventListener) {
+ super(command, executorService, shutdownOnExit, sendSize, receiveSize, opener, eventListener);
+ }
+
+ @Override
+ protected void writeCommandResponseMessage(String command, int exitValue, String exitMessage) throws IOException {
+ outputDebugMessage("writeCommandResponseMessage(%s) status=%d", command, exitValue);
+ super.writeCommandResponseMessage(command, testExitValue, exitMessage);
+ }
+
+ @Override
+ public void setExitCallback(ExitCallback callback) {
+ delegate = callback;
+ super.setExitCallback(this);
+ }
+
+ @Override
+ public void onExit(int exitValue) {
+ onExit(exitValue, Integer.toString(exitValue));
+ }
+
+ @Override
+ public void onExit(int exitValue, String exitMessage) {
+ outputDebugMessage("onExit(%s) status=%d", this, exitValue);
+ if (exitValue == ScpHelper.OK) {
+ delegate.onExit(testExitValue, exitMessage);
+ } else {
+ delegate.onExit(exitValue, exitMessage);
+ }
+ }
+ }
+
+ ScpCommandFactory factory = (ScpCommandFactory) sshd.getCommandFactory();
+ sshd.setCommandFactory(new ScpCommandFactory() {
+ @Override
+ public Command createCommand(String command) {
+ ValidateUtils.checkTrue(command.startsWith(ScpHelper.SCP_COMMAND_PREFIX), "Bad SCP command: %s", command);
+ return new InternalScpCommand(command,
+ getExecutorService(), isShutdownOnExit(),
+ getSendBufferSize(), getReceiveBufferSize(),
+ DefaultScpFileOpener.INSTANCE,
+ ScpTransferEventListener.EMPTY);
+ }
+ });
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(13L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(11L, TimeUnit.SECONDS);
+
+ ScpClientCreator creator = ScpClientCreator.instance();
+ ScpClient scp = creator.createScpClient(session);
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve("file.txt");
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ outputDebugMessage("Upload data to %s", remotePath);
+ try {
+ scp.upload(data, remotePath, EnumSet.allOf(PosixFilePermission.class), null);
+ outputDebugMessage("Upload success to %s", remotePath);
+ } catch (ScpException e) {
+ Integer exitCode = e.getExitStatus();
+ assertNotNull("No upload exit status", exitCode);
+ assertEquals("Mismatched upload exit status", testExitValue, exitCode.intValue());
+ }
+
+ if (Files.deleteIfExists(remoteFile)) {
+ outputDebugMessage("Deleted remote file %s", remoteFile);
+ }
+
+ try (OutputStream out = Files.newOutputStream(remoteFile)) {
+ out.write(data);
+ }
+
+ try {
+ byte[] downloaded = scp.downloadBytes(remotePath);
+ outputDebugMessage("Download success to %s: %s", remotePath, new String(downloaded, StandardCharsets.UTF_8));
+ } catch (ScpException e) {
+ Integer exitCode = e.getExitStatus();
+ assertNotNull("No download exit status", exitCode);
+ assertEquals("Mismatched download exit status", testExitValue, exitCode.intValue());
+ }
+ } finally {
+ sshd.setCommandFactory(factory);
+ }
+ }
+
+ // see http://stackoverflow.com/questions/2717936/file-createnewfile-creates-files-with-last-modified-time-before-actual-creatio
+ // See https://msdn.microsoft.com/en-us/library/ms724290(VS.85).aspx
+ private static void assertLastModifiedTimeEquals(File file, boolean modSuccess, long expectedSeconds) {
+ long expectedMillis = TimeUnit.SECONDS.toMillis(expectedSeconds);
+ long actualMillis = file.lastModified();
+ long actualSeconds = TimeUnit.MILLISECONDS.toSeconds(actualMillis);
+ // if failed to set the local file time, don't expect it to be the same
+ if (!modSuccess) {
+ System.err.append("Failed to set last modified time of ").append(file.getAbsolutePath())
+ .append(" to ").append(String.valueOf(expectedMillis))
+ .append(" - ").println(new Date(expectedMillis));
+ System.err.append("\t\t").append("Current value: ").append(String.valueOf(actualMillis))
+ .append(" - ").println(new Date(actualMillis));
+ return;
+ }
+
+ if (OsUtils.isWin32()) {
+ // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last access
+ if (expectedSeconds != actualSeconds) {
+ System.err.append("Mismatched last modified time for ").append(file.getAbsolutePath())
+ .append(" - expected=").append(String.valueOf(expectedSeconds))
+ .append('[').append(new Date(expectedMillis).toString()).append(']')
+ .append(", actual=").append(String.valueOf(actualSeconds))
+ .append('[').append(new Date(actualMillis).toString()).append(']')
+ .println();
+ }
+ } else {
+ assertEquals("Mismatched last modified time for " + file.getAbsolutePath(), expectedSeconds, actualSeconds);
+ }
+ }
+
+ @Test
+ public void testJschScp() throws Exception {
+ com.jcraft.jsch.Session session = getJschSession();
+ try {
+ String data = getCurrentTestName() + "\n";
+
+ String unixDir = "target/scp";
+ String fileName = getCurrentTestName() + ".txt";
+ String unixPath = unixDir + "/" + fileName;
+ File root = new File(unixDir);
+ File target = new File(unixPath);
+ Utils.deleteRecursive(root);
+ root.mkdirs();
+ assertTrue("Failed to ensure existence of " + root, root.exists());
+
+ target.delete();
+ assertFalse("Failed to delete 1st time: " + target, target.exists());
+ sendFile(session, unixPath, target, data);
+ assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(11L));
+
+ target.delete();
+ assertFalse("Failed to delete 2nd time: " + target, target.exists());
+ sendFile(session, unixDir, target, data);
+ assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(11L));
+
+ sendFileError(session, "target", ScpHelper.SCP_COMMAND_PREFIX, data);
+
+ readFileError(session, unixDir);
+
+ assertEquals("Mismatched file data", data, readFile(session, unixPath, target));
+ assertEquals("Mismatched dir data", data, readDir(session, unixDir, target));
+
+ target.delete();
+ root.delete();
+
+ sendDir(session, "target", ScpHelper.SCP_COMMAND_PREFIX, fileName, data);
+ assertFileLength(target, data.length(), TimeUnit.SECONDS.toMillis(11L));
+ } finally {
+ session.disconnect();
+ }
+ }
+
+ protected com.jcraft.jsch.Session getJschSession() throws JSchException {
+ JSch sch = new JSch();
+ com.jcraft.jsch.Session session = sch.getSession(getCurrentTestName(), TEST_LOCALHOST, port);
+ session.setUserInfo(new SimpleUserInfo(getCurrentTestName()));
+ session.connect();
+ return session;
+ }
+
+ @Test
+ public void testWithGanymede() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ byte[] expected = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteDir);
+ String fileName = "file.txt";
+ Path remoteFile = remoteDir.resolve(fileName);
+ String mode = ScpHelper.getOctalPermissions(EnumSet.of(
+ PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_WRITE,
+ PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE,
+ PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE
+ ));
+
+ ch.ethz.ssh2.log.Logger.enabled = true;
+ Connection conn = new Connection(TEST_LOCALHOST, port);
+ try {
+ ConnectionInfo info = conn.connect(null, (int) TimeUnit.SECONDS.toMillis(5L), (int) TimeUnit.SECONDS.toMillis(13L));
+ outputDebugMessage("Connected: kex=%s, key-type=%s, c2senc=%s, s2cenc=%s, c2mac=%s, s2cmac=%s",
+ info.keyExchangeAlgorithm, info.serverHostKeyAlgorithm,
+ info.clientToServerCryptoAlgorithm, info.serverToClientCryptoAlgorithm,
+ info.clientToServerMACAlgorithm, info.serverToClientMACAlgorithm);
+ assertTrue("Failed to authenticate", conn.authenticateWithPassword(getCurrentTestName(), getCurrentTestName()));
+
+ SCPClient scpClient = new SCPClient(conn);
+ try (OutputStream output = scpClient.put(fileName, expected.length, remotePath, mode)) {
+ output.write(expected);
+ }
+
+ assertTrue("Remote file not created: " + remoteFile, Files.exists(remoteFile));
+ byte[] remoteData = Files.readAllBytes(remoteFile);
+ assertArrayEquals("Mismatched remote put data", expected, remoteData);
+
+ Arrays.fill(remoteData, (byte) 0); // make sure we start with a clean slate
+ try (InputStream input = scpClient.get(remotePath + "/" + fileName)) {
+ int readLen = input.read(remoteData);
+ assertEquals("Mismatched remote get data size", expected.length, readLen);
+ // make sure we reached EOF
+ assertEquals("Unexpected extra data after read expected size", -1, input.read());
+ }
+
+ assertArrayEquals("Mismatched remote get data", expected, remoteData);
+ } finally {
+ conn.close();
+ }
+ }
+
+ protected String readFile(com.jcraft.jsch.Session session, String path, File target) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ c.setCommand("scp -f " + path);
+ c.connect();
+
+ String fileName = target.getName();
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+
+ os.write(0);
+ os.flush();
+
+ String header = readLine(is);
+ String expHeader = "C" + ScpHelper.DEFAULT_FILE_OCTAL_PERMISSIONS + " " + target.length() + " " + fileName;
+ assertEquals("Mismatched header for " + path, expHeader, header);
+
+ String lenValue = header.substring(6, header.indexOf(' ', 6));
+ int length = Integer.parseInt(lenValue);
+ os.write(0);
+ os.flush();
+
+ byte[] buffer = new byte[length];
+ length = is.read(buffer, 0, buffer.length);
+ assertEquals("Mismatched read data length for " + path, length, buffer.length);
+ assertAckReceived(is, "Read data of " + path);
+
+ os.write(0);
+ os.flush();
+
+ return new String(buffer, StandardCharsets.UTF_8);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected String readDir(com.jcraft.jsch.Session session, String path, File target) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ c.setCommand("scp -r -f " + path);
+ c.connect();
+
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+ os.write(0);
+ os.flush();
+
+ String header = readLine(is);
+ String expPrefix = "D" + ScpHelper.DEFAULT_DIR_OCTAL_PERMISSIONS + " 0 ";
+ assertTrue("Bad header prefix for " + path + ": " + header, header.startsWith(expPrefix));
+ os.write(0);
+ os.flush();
+
+ header = readLine(is);
+ String expHeader = "C" + ScpHelper.DEFAULT_FILE_OCTAL_PERMISSIONS + " " + target.length() + " " + target.getName();
+ assertEquals("Mismatched dir header for " + path, expHeader, header);
+ int length = Integer.parseInt(header.substring(6, header.indexOf(' ', 6)));
+ os.write(0);
+ os.flush();
+
+ byte[] buffer = new byte[length];
+ length = is.read(buffer, 0, buffer.length);
+ assertEquals("Mismatched read buffer size for " + path, length, buffer.length);
+ assertAckReceived(is, "Read date of " + path);
+
+ os.write(0);
+ os.flush();
+
+ header = readLine(is);
+ assertEquals("Mismatched end value for " + path, "E", header);
+ os.write(0);
+ os.flush();
+
+ return new String(buffer, StandardCharsets.UTF_8);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected void readFileError(com.jcraft.jsch.Session session, String path) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ String command = "scp -f " + path;
+ c.setCommand(command);
+ c.connect();
+
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+
+ os.write(0);
+ os.flush();
+ assertEquals("Mismatched response for command: " + command, 2, is.read());
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected void sendFile(com.jcraft.jsch.Session session, String path, File target, String data) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ String command = "scp -t " + path;
+ c.setCommand(command);
+ c.connect();
+
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+
+ assertAckReceived(is, command);
+
+ File parent = target.getParentFile();
+ Collection<PosixFilePermission> perms = IoUtils.getPermissions(parent.toPath());
+ String octalPerms = ScpHelper.getOctalPermissions(perms);
+ String name = target.getName();
+ assertAckReceived(os, is, "C" + octalPerms + " " + data.length() + " " + name);
+
+ os.write(data.getBytes(StandardCharsets.UTF_8));
+ os.flush();
+ assertAckReceived(is, "Sent data (length=" + data.length() + ") for " + path + "[" + name + "]");
+
+ os.write(0);
+ os.flush();
+
+ Thread.sleep(100);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected void assertAckReceived(OutputStream os, InputStream is, String command) throws IOException {
+ os.write((command + "\n").getBytes(StandardCharsets.UTF_8));
+ os.flush();
+ assertAckReceived(is, command);
+ }
+
+ protected void assertAckReceived(InputStream is, String command) throws IOException {
+ assertEquals("No ACK for command=" + command, 0, is.read());
+ }
+
+ protected void sendFileError(com.jcraft.jsch.Session session, String path, String name, String data) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ String command = "scp -t " + path;
+ c.setCommand(command);
+ c.connect();
+
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+
+ assertAckReceived(is, command);
+
+ command = "C7777 " + data.length() + " " + name;
+ os.write((command + "\n").getBytes(StandardCharsets.UTF_8));
+ os.flush();
+ assertEquals("Mismatched response for command=" + command, 2, is.read());
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ protected void sendDir(com.jcraft.jsch.Session session, String path, String dirName, String fileName, String data) throws Exception {
+ ChannelExec c = (ChannelExec) session.openChannel(Channel.CHANNEL_EXEC);
+ String command = "scp -t -r " + path;
+ c.setCommand(command);
+ c.connect();
+
+ try (OutputStream os = c.getOutputStream();
+ InputStream is = c.getInputStream()) {
+
+ assertAckReceived(is, command);
+ assertAckReceived(os, is, "D0755 0 " + dirName);
+ assertAckReceived(os, is, "C7777 " + data.length() + " " + fileName);
+
+ os.write(data.getBytes(StandardCharsets.UTF_8));
+ os.flush();
+ assertAckReceived(is, "Send data of " + path);
+
+ os.write(0);
+ os.flush();
+
+ os.write("E\n".getBytes(StandardCharsets.UTF_8));
+ os.flush();
+ assertAckReceived(is, "Signal end of " + path);
+ } finally {
+ c.disconnect();
+ }
+ }
+
+ private static String readLine(InputStream in) throws IOException {
+ try (OutputStream 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);
+ }
+ }
+ }
+ }
+
+ private static ScpClient createScpClient(ClientSession session) {
+ ScpClientCreator creator = ScpClientCreator.instance();
+ ScpTransferEventListener listener = getScpTransferEventListener(session);
+ return creator.createScpClient(session, listener);
+ }
+
+ private static ScpTransferEventListener getScpTransferEventListener(ClientSession session) {
+ return OUTPUT_DEBUG_MESSAGES ? DEBUG_LISTENER : ScpTransferEventListener.EMPTY;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/af415e5f/sshd-scp/src/test/java/org/apache/sshd/client/scp/SimpleScpClientTest.java
----------------------------------------------------------------------
diff --git a/sshd-scp/src/test/java/org/apache/sshd/client/scp/SimpleScpClientTest.java b/sshd-scp/src/test/java/org/apache/sshd/client/scp/SimpleScpClientTest.java
new file mode 100644
index 0000000..d9b9b08
--- /dev/null
+++ b/sshd-scp/src/test/java/org/apache/sshd/client/scp/SimpleScpClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.scp;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.scp.ScpCommandFactory;
+import org.apache.sshd.util.test.Utils;
+import org.apache.sshd.util.test.client.simple.BaseSimpleClientTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SimpleScpClientTest extends BaseSimpleClientTestSupport {
+ private final Path targetPath;
+ private final Path parentPath;
+ private final FileSystemFactory fileSystemFactory;
+ private SimpleScpClient scpClient;
+
+ public SimpleScpClientTest() throws Exception {
+ targetPath = detectTargetFolder();
+ parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.setFileSystemFactory(fileSystemFactory);
+ client.start();
+ scpClient = new SimpleScpClientImpl(simple);
+ }
+
+ @Test
+ public void testSessionClosedWhenClientClosed() throws Exception {
+ try (CloseableScpClient scp = login()) {
+ assertTrue("SCP not open", scp.isOpen());
+
+ Session session = scp.getClientSession();
+ assertTrue("Session not open", session.isOpen());
+
+ scp.close();
+ assertFalse("Session not closed", session.isOpen());
+ assertFalse("SCP not closed", scp.isOpen());
+ }
+ }
+
+ @Test
+ public void testScpUploadProxy() throws Exception {
+ try (CloseableScpClient scp = login()) {
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localFile = localDir.resolve("file.txt");
+ String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
+ byte[] written = Utils.writeFile(localFile, data);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve(localFile.getFileName());
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ scp.upload(localFile, remotePath);
+
+ byte[] uploaded = Files.readAllBytes(remoteFile);
+ assertArrayEquals("Mismatched uploaded data", written, uploaded);
+ }
+ }
+
+ @Test
+ public void testScpDownloadProxy() throws Exception {
+ try (CloseableScpClient scp = login()) {
+ Path scpRoot = Utils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(scpRoot);
+
+ Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote"));
+ Path remoteFile = remoteDir.resolve("file.txt");
+ String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL;
+ byte[] written = Utils.writeFile(remoteFile, data);
+ Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local"));
+ Path localFile = localDir.resolve(remoteFile.getFileName());
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, remoteFile);
+ scp.download(remotePath, localFile);
+
+ byte[] downloaded = Files.readAllBytes(localFile);
+ assertArrayEquals("Mismatched downloaded data", written, downloaded);
+ }
+ }
+
+ private CloseableScpClient login() throws IOException {
+ return scpClient.scpLogin(TEST_LOCALHOST, port, getCurrentTestName(), getCurrentTestName());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/af415e5f/sshd-scp/src/test/java/org/apache/sshd/server/scp/ScpCommandFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-scp/src/test/java/org/apache/sshd/server/scp/ScpCommandFactoryTest.java b/sshd-scp/src/test/java/org/apache/sshd/server/scp/ScpCommandFactoryTest.java
new file mode 100644
index 0000000..0cd1e91
--- /dev/null
+++ b/sshd-scp/src/test/java/org/apache/sshd/server/scp/ScpCommandFactoryTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server.scp;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.sshd.common.scp.ScpHelper;
+import org.apache.sshd.server.CommandFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class ScpCommandFactoryTest extends BaseTestSupport {
+ public ScpCommandFactoryTest() {
+ super();
+ }
+
+ /**
+ * Make sure that the builder returns a factory with the default values
+ * if no {@code withXXX} method is invoked
+ */
+ @Test
+ public void testBuilderDefaultFactoryValues() {
+ ScpCommandFactory factory = new ScpCommandFactory.Builder().build();
+ assertNull("Mismatched delegate", factory.getDelegateCommandFactory());
+ assertNull("Mismatched executor", factory.getExecutorService());
+ assertEquals("Mismatched send size", ScpHelper.MIN_SEND_BUFFER_SIZE, factory.getSendBufferSize());
+ assertEquals("Mismatched receive size", ScpHelper.MIN_RECEIVE_BUFFER_SIZE, factory.getReceiveBufferSize());
+ assertFalse("Mismatched shutdown state", factory.isShutdownOnExit());
+ }
+
+ /**
+ * Make sure that the builder initializes correctly the built factory
+ */
+ @Test
+ public void testBuilderCorrectlyInitializesFactory() {
+ CommandFactory delegate = dummyFactory();
+ ExecutorService service = dummyExecutor();
+ int receiveSize = Short.MAX_VALUE;
+ int sendSize = receiveSize + Long.SIZE;
+ ScpCommandFactory factory = new ScpCommandFactory.Builder()
+ .withDelegate(delegate)
+ .withExecutorService(service)
+ .withSendBufferSize(sendSize)
+ .withReceiveBufferSize(receiveSize)
+ .withShutdownOnExit(true)
+ .build();
+ assertSame("Mismatched delegate", delegate, factory.getDelegateCommandFactory());
+ assertSame("Mismatched executor", service, factory.getExecutorService());
+ assertEquals("Mismatched send size", sendSize, factory.getSendBufferSize());
+ assertEquals("Mismatched receive size", receiveSize, factory.getReceiveBufferSize());
+ assertTrue("Mismatched shutdown state", factory.isShutdownOnExit());
+ }
+
+ /**
+ * <UL>
+ * <LI>
+ * Make sure the builder returns new instances on every call to
+ * {@link org.apache.sshd.server.scp.ScpCommandFactory.Builder#build()} method
+ * </LI>
+ *
+ * <LI>
+ * Make sure values are preserved between successive invocations
+ * of the {@link org.apache.sshd.server.scp.ScpCommandFactory.Builder#build()} method
+ * </LI>
+ * </UL
+ */
+ @Test
+ public void testBuilderUniqueInstance() {
+ ScpCommandFactory.Builder builder = new ScpCommandFactory.Builder();
+ ScpCommandFactory f1 = builder.withDelegate(dummyFactory()).build();
+ ScpCommandFactory f2 = builder.build();
+ assertNotSame("No new instance built", f1, f2);
+ assertSame("Mismatched delegate", f1.getDelegateCommandFactory(), f2.getDelegateCommandFactory());
+
+ ScpCommandFactory f3 = builder.withDelegate(dummyFactory()).build();
+ assertNotSame("Delegate not changed", f1.getDelegateCommandFactory(), f3.getDelegateCommandFactory());
+ }
+
+ private static ExecutorService dummyExecutor() {
+ return Mockito.mock(ExecutorService.class);
+ }
+
+ private static CommandFactory dummyFactory() {
+ return Mockito.mock(CommandFactory.class);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/af415e5f/sshd-sftp/src/main/java/org/apache/sshd/client/simple/SimpleSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/simple/SimpleSftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/client/simple/SimpleSftpClient.java
deleted file mode 100644
index 1034046..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/simple/SimpleSftpClient.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.simple;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.nio.channels.Channel;
-import java.security.KeyPair;
-import java.util.Objects;
-
-import org.apache.sshd.client.subsystem.sftp.SftpClient;
-import org.apache.sshd.common.util.ValidateUtils;
-
-/**
- * A simplified <U>synchronous</U> API for obtaining SFTP sessions.
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public interface SimpleSftpClient extends Channel {
- /**
- * Creates an SFTP session on the default port and logs in using the provided credentials
- *
- * @param host The target host name or address
- * @param username Username
- * @param password Password
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(String host, String username, String password) throws IOException {
- return sftpLogin(host, SimpleClientConfigurator.DEFAULT_PORT, username, password);
- }
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param host The target host name or address
- * @param port The target port
- * @param username Username
- * @param password Password
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(String host, int port, String username, String password) throws IOException {
- return sftpLogin(InetAddress.getByName(ValidateUtils.checkNotNullAndNotEmpty(host, "No host")), port, username, password);
- }
-
- /**
- * Creates an SFTP session on the default port and logs in using the provided credentials
- *
- * @param host The target host name or address
- * @param username Username
- * @param identity The {@link KeyPair} identity
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(String host, String username, KeyPair identity) throws IOException {
- return sftpLogin(host, SimpleClientConfigurator.DEFAULT_PORT, username, identity);
- }
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param host The target host name or address
- * @param port The target port
- * @param username Username
- * @param identity The {@link KeyPair} identity
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(String host, int port, String username, KeyPair identity) throws IOException {
- return sftpLogin(InetAddress.getByName(ValidateUtils.checkNotNullAndNotEmpty(host, "No host")), port, username, identity);
- }
-
- /**
- * Creates an SFTP session on the default port and logs in using the provided credentials
- *
- * @param host The target host {@link InetAddress}
- * @param username Username
- * @param password Password
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(InetAddress host, String username, String password) throws IOException {
- return sftpLogin(host, SimpleClientConfigurator.DEFAULT_PORT, username, password);
- }
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param host The target host {@link InetAddress}
- * @param port The target port
- * @param username Username
- * @param password Password
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(InetAddress host, int port, String username, String password) throws IOException {
- return sftpLogin(new InetSocketAddress(Objects.requireNonNull(host, "No host address"), port), username, password);
- }
-
- /**
- * Creates an SFTP session on the default port and logs in using the provided credentials
- *
- * @param host The target host {@link InetAddress}
- * @param username Username
- * @param identity The {@link KeyPair} identity
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(InetAddress host, String username, KeyPair identity) throws IOException {
- return sftpLogin(host, SimpleClientConfigurator.DEFAULT_PORT, username, identity);
- }
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param host The target host {@link InetAddress}
- * @param port The target port
- * @param username Username
- * @param identity The {@link KeyPair} identity
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- default SftpClient sftpLogin(InetAddress host, int port, String username, KeyPair identity) throws IOException {
- return sftpLogin(new InetSocketAddress(Objects.requireNonNull(host, "No host address"), port), username, identity);
- }
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param target The target {@link SocketAddress}
- * @param username Username
- * @param password Password
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- SftpClient sftpLogin(SocketAddress target, String username, String password) throws IOException;
-
- /**
- * Creates an SFTP session using the provided credentials
- *
- * @param target The target {@link SocketAddress}
- * @param username Username
- * @param identity The {@link KeyPair} identity
- * @return Created {@link SftpClient} - <B>Note:</B> closing the client also closes its
- * underlying session
- * @throws IOException If failed to login or authenticate
- */
- SftpClient sftpLogin(SocketAddress target, String username, KeyPair identity) throws IOException;
-
-}