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 2017/12/18 17:55:37 UTC

mina-sshd git commit: [SSHD-787] Provide fine-grained control over SCP stream paths resolution

Repository: mina-sshd
Updated Branches:
  refs/heads/master 014bf86c5 -> 1cd204b7a


[SSHD-787] Provide fine-grained control over SCP stream paths resolution


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1cd204b7
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1cd204b7
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1cd204b7

Branch: refs/heads/master
Commit: 1cd204b7ad221d74211d67b3a4659b6210a6fa56
Parents: 014bf86
Author: Lyor Goldstein <ly...@gmail.com>
Authored: Fri Dec 15 19:05:27 2017 +0200
Committer: Lyor Goldstein <ly...@gmail.com>
Committed: Mon Dec 18 19:57:33 2017 +0200

----------------------------------------------------------------------
 .../sshd/client/ClientFactoryManager.java       |  2 +
 .../java/org/apache/sshd/client/SshClient.java  | 12 +++++
 .../sshd/client/scp/DefaultScpClient.java       | 16 +++++--
 .../sshd/client/scp/ScpClientCreator.java       | 29 +++++++++---
 .../client/session/AbstractClientSession.java   | 16 ++++++-
 .../sshd/client/subsystem/sftp/SftpCommand.java |  2 +-
 .../org/apache/sshd/common/scp/ScpHelper.java   | 18 +++----
 .../common/scp/ScpStreamResolverFactory.java    | 32 +++++++++++++
 .../scp/ScpStreamResolverFactoryHolder.java     | 29 ++++++++++++
 .../DefaultScpStreamResolverFactory.java        | 50 ++++++++++++++++++++
 .../org/apache/sshd/server/scp/ScpCommand.java  | 10 ++--
 .../sshd/server/scp/ScpCommandFactory.java      | 27 ++++++++++-
 .../org/apache/sshd/client/scp/ScpTest.java     | 11 +++--
 13 files changed, 224 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 31b2a22..e116651 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -24,6 +24,7 @@ import org.apache.sshd.client.session.ClientProxyConnectorHolder;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.config.keys.FilePasswordProvider;
 import org.apache.sshd.common.scp.ScpFileOpenerHolder;
+import org.apache.sshd.common.scp.ScpStreamResolverFactoryHolder;
 
 /**
  * The <code>ClientFactoryManager</code> enable the retrieval of additional
@@ -34,6 +35,7 @@ import org.apache.sshd.common.scp.ScpFileOpenerHolder;
 public interface ClientFactoryManager
         extends FactoryManager,
                 ScpFileOpenerHolder,
+                ScpStreamResolverFactoryHolder,
                 ClientProxyConnectorHolder,
                 ClientAuthenticationManager {
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
index 0e8979e..a23d098 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java
@@ -115,6 +115,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.mac.BuiltinMacs;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpStreamResolverFactory;
 import org.apache.sshd.common.session.helpers.AbstractSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
@@ -211,6 +212,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     private FilePasswordProvider filePasswordProvider;
     private PasswordIdentityProvider passwordIdentityProvider;
     private ScpFileOpener scpOpener;
+    private ScpStreamResolverFactory scpStreamFactory;
 
     private final List<Object> identities = new CopyOnWriteArrayList<>();
     private final AuthenticationIdentitiesProvider identitiesProvider;
@@ -248,6 +250,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa
     }
 
     @Override
+    public ScpStreamResolverFactory getScpStreamResolverFactory() {
+        return scpStreamFactory;
+    }
+
+    @Override
+    public void setScpStreamResolverFactory(ScpStreamResolverFactory factory) {
+        scpStreamFactory = factory;
+    }
+
+    @Override
     public ServerKeyVerifier getServerKeyVerifier() {
         return serverKeyVerifier;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
index d69252c..ead4c52 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java
@@ -47,9 +47,11 @@ import org.apache.sshd.common.file.util.MockPath;
 import org.apache.sshd.common.scp.ScpFileOpener;
 import org.apache.sshd.common.scp.ScpHelper;
 import org.apache.sshd.common.scp.ScpLocation;
+import org.apache.sshd.common.scp.ScpStreamResolverFactory;
 import org.apache.sshd.common.scp.ScpTimestamp;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
+import org.apache.sshd.common.scp.helpers.DefaultScpStreamResolverFactory;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.NoCloseInputStream;
@@ -64,12 +66,16 @@ public class DefaultScpClient extends AbstractScpClient {
     public static final String SCP_PORT_OPTION = "-P";
 
     protected final ScpFileOpener opener;
+    protected final ScpStreamResolverFactory streamFactory;
     protected final ScpTransferEventListener listener;
     private final ClientSession clientSession;
 
-    public DefaultScpClient(ClientSession clientSession, ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
+    public DefaultScpClient(
+            ClientSession clientSession, ScpFileOpener fileOpener,
+            ScpStreamResolverFactory streamFactory, ScpTransferEventListener eventListener) {
         this.clientSession = Objects.requireNonNull(clientSession, "No client session");
         this.opener = (fileOpener == null) ? DefaultScpFileOpener.INSTANCE : fileOpener;
+        this.streamFactory = (streamFactory == null) ? DefaultScpStreamResolverFactory.INSTANCE : streamFactory;
         this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
     }
 
@@ -86,7 +92,7 @@ public class DefaultScpClient extends AbstractScpClient {
         try (InputStream invOut = channel.getInvertedOut();
              OutputStream invIn = channel.getInvertedIn()) {
             // NOTE: we use a mock file system since we expect no invocations for it
-            ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, listener);
+            ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, streamFactory, listener);
             helper.receiveFileStream(local, ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
             handleCommandExitStatus(cmd, channel);
         } finally {
@@ -101,7 +107,7 @@ public class DefaultScpClient extends AbstractScpClient {
         ChannelExec channel = openCommandChannel(session, cmd);
         try (InputStream invOut = channel.getInvertedOut();
              OutputStream invIn = channel.getInvertedIn()) {
-            ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, listener);
+            ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, streamFactory, listener);
             helper.receive(local,
                     options.contains(Option.Recursive),
                     options.contains(Option.TargetIsDirectory),
@@ -126,7 +132,7 @@ public class DefaultScpClient extends AbstractScpClient {
         try (InputStream invOut = channel.getInvertedOut();
              OutputStream invIn = channel.getInvertedIn()) {
             // NOTE: we use a mock file system since we expect no invocations for it
-            ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, listener);
+            ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, streamFactory, listener);
             Path mockPath = new MockPath(remote);
             helper.sendStream(new DefaultScpStreamResolver(name, mockPath, perms, time, size, local, cmd),
                     options.contains(Option.PreserveAttributes), ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
@@ -154,7 +160,7 @@ public class DefaultScpClient extends AbstractScpClient {
 
             try (InputStream invOut = channel.getInvertedOut();
                  OutputStream invIn = channel.getInvertedIn()) {
-                ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, listener);
+                ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, streamFactory, listener);
                 executor.execute(helper, local, options);
             } finally {
                 try {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java
index b3199b2..502e5aa 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java
@@ -21,22 +21,26 @@ package org.apache.sshd.client.scp;
 
 import org.apache.sshd.common.scp.ScpFileOpener;
 import org.apache.sshd.common.scp.ScpFileOpenerHolder;
+import org.apache.sshd.common.scp.ScpStreamResolverFactory;
+import org.apache.sshd.common.scp.ScpStreamResolverFactoryHolder;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public interface ScpClientCreator extends ScpFileOpenerHolder {
+public interface ScpClientCreator extends ScpFileOpenerHolder, ScpStreamResolverFactoryHolder {
     /**
      * Create an SCP client from this session.
      *
      * @return An {@link ScpClient} instance. <B>Note:</B> uses the currently
-     * registered {@link ScpTransferEventListener} and {@link ScpFileOpener} if any
+     * registered {@link ScpTransferEventListener}, {@link ScpStreamResolverFactoryHolder}
+     *  and {@link ScpFileOpener} if any
      * @see #setScpFileOpener(ScpFileOpener)
+     * @see #setScpStreamResolverFactory(ScpStreamResolverFactory)
      * @see #setScpTransferEventListener(ScpTransferEventListener)
      */
     default ScpClient createScpClient() {
-        return createScpClient(getScpFileOpener(), getScpTransferEventListener());
+        return createScpClient(getScpFileOpener(), getScpStreamResolverFactory(), getScpTransferEventListener());
     }
 
     /**
@@ -49,7 +53,7 @@ public interface ScpClientCreator extends ScpFileOpenerHolder {
      * @return An {@link ScpClient} instance
      */
     default ScpClient createScpClient(ScpTransferEventListener listener) {
-        return createScpClient(getScpFileOpener(), listener);
+        return createScpClient(getScpFileOpener(), getScpStreamResolverFactory(), listener);
     }
 
     /**
@@ -62,7 +66,18 @@ public interface ScpClientCreator extends ScpFileOpenerHolder {
      * @return An {@link ScpClient} instance
      */
     default ScpClient createScpClient(ScpFileOpener opener) {
-        return createScpClient(opener, getScpTransferEventListener());
+        return createScpClient(opener, getScpStreamResolverFactory(), getScpTransferEventListener());
+    }
+
+    /**
+     * Create an SCP client from this session.
+     *
+     * @param factory The {@link ScpStreamResolverFactory} used to create input/output stream
+     *                for incoming/outgoing files
+     * @return An {@link ScpClient} instance
+     */
+    default ScpClient createScpClient(ScpStreamResolverFactory factory) {
+        return createScpClient(getScpFileOpener(), factory, getScpTransferEventListener());
     }
 
     /**
@@ -72,13 +87,15 @@ public interface ScpClientCreator extends ScpFileOpenerHolder {
      *                 are read/written. If {@code null} then a default opener is used.
      *                 <B>Note:</B> this opener is used <U>instead</U> of any instance
      *                 set via {@link #setScpFileOpener(ScpFileOpener)}
+     * @param factory  The {@link ScpStreamResolverFactory} to use in order to create
+     *                 incoming/outgoing streams for received/sent files
      * @param listener A {@link ScpTransferEventListener} that can be used
      *                 to receive information about the SCP operations - may be {@code null}
      *                 to indicate no more events are required. <B>Note:</B> this listener
      *                 is used <U>instead</U> of any listener set via {@link #setScpTransferEventListener(ScpTransferEventListener)}
      * @return An {@link ScpClient} instance
      */
-    ScpClient createScpClient(ScpFileOpener opener, ScpTransferEventListener listener);
+    ScpClient createScpClient(ScpFileOpener opener, ScpStreamResolverFactory factory, ScpTransferEventListener listener);
 
     /**
      * @return The last {@link ScpTransferEventListener} set via

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
index b67310d..ee8c550 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java
@@ -66,6 +66,7 @@ import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.kex.KexProposalOption;
 import org.apache.sshd.common.kex.KexState;
 import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpStreamResolverFactory;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
@@ -90,6 +91,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
     private List<NamedFactory<UserAuth>> userAuthFactories;
     private ScpTransferEventListener scpListener;
     private ScpFileOpener scpOpener;
+    private ScpStreamResolverFactory scpStreamFactory;
     private SocketAddress connectAddress;
     private ClientProxyConnector proxyConnector;
 
@@ -317,6 +319,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C
     }
 
     @Override
+    public ScpStreamResolverFactory getScpStreamResolverFactory() {
+        return resolveEffectiveProvider(ScpStreamResolverFactory.class, scpStreamFactory, getFactoryManager().getScpStreamResolverFactory());
+    }
+
+    @Override
+    public void setScpStreamResolverFactory(ScpStreamResolverFactory factory) {
+        scpStreamFactory = factory;
+    }
+
+    @Override
     public ScpTransferEventListener getScpTransferEventListener() {
         return scpListener;
     }
@@ -327,8 +339,8 @@ public abstract class AbstractClientSession extends AbstractSession implements C
     }
 
     @Override
-    public ScpClient createScpClient(ScpFileOpener opener, ScpTransferEventListener listener) {
-        return new DefaultScpClient(this, opener, listener);
+    public ScpClient createScpClient(ScpFileOpener opener, ScpStreamResolverFactory factory, ScpTransferEventListener listener) {
+        return new DefaultScpClient(this, opener, factory, listener);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
index 4865431..3b8e88f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
@@ -81,7 +81,7 @@ public class SftpCommand implements Channel {
     public SftpCommand(SftpClient client) {
         this.client = Objects.requireNonNull(client, "No client");
 
-        Map<String, CommandExecutor> map = new TreeMap<>();
+        Map<String, CommandExecutor> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         for (CommandExecutor e : Arrays.asList(
                 new ExitCommandExecutor(),
                 new PwdCommandExecutor(),

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
index 9918e02..0e75e83 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java
@@ -48,8 +48,7 @@ import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.file.util.MockPath;
 import org.apache.sshd.common.scp.ScpTransferEventListener.FileOperation;
 import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
-import org.apache.sshd.common.scp.helpers.LocalFileScpSourceStreamResolver;
-import org.apache.sshd.common.scp.helpers.LocalFileScpTargetStreamResolver;
+import org.apache.sshd.common.scp.helpers.DefaultScpStreamResolverFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.GenericUtils;
@@ -104,17 +103,20 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
     protected final OutputStream out;
     protected final FileSystem fileSystem;
     protected final ScpFileOpener opener;
+    protected final ScpStreamResolverFactory streamFactory;
     protected final ScpTransferEventListener listener;
 
     private final Session sessionInstance;
 
     public ScpHelper(Session session, InputStream in, OutputStream out,
-            FileSystem fileSystem, ScpFileOpener opener, ScpTransferEventListener eventListener) {
+            FileSystem fileSystem, ScpFileOpener opener,
+            ScpStreamResolverFactory streamFactory, ScpTransferEventListener eventListener) {
         this.sessionInstance = Objects.requireNonNull(session, "No session");
         this.in = Objects.requireNonNull(in, "No input stream");
         this.out = Objects.requireNonNull(out, "No output stream");
         this.fileSystem = fileSystem;
         this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
+        this.streamFactory = (streamFactory == null) ? DefaultScpStreamResolverFactory.INSTANCE : streamFactory;
         this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
     }
 
@@ -129,7 +131,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                 throw new StreamCorruptedException("Cannot download a directory into a file stream: " + line);
             }
 
-            final Path path = new MockPath(line);
+            Path path = new MockPath(line);
             receiveStream(line, new ScpTargetStreamResolver() {
                 @SuppressWarnings("synthetic-access")
                 @Override
@@ -147,8 +149,8 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                     return path;
                 }
 
-                @Override
                 @SuppressWarnings("synthetic-access")
+                @Override
                 public void postProcessReceivedData(String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
                     if (log.isDebugEnabled()) {
                         log.debug("postProcessReceivedData({}) name={}, perms={}, preserve={} time={}",
@@ -164,7 +166,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
         });
     }
 
-    public void receive(Path local, boolean recursive, boolean shouldBeDir, boolean preserve, final int bufferSize) throws IOException {
+    public void receive(Path local, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException {
         Path path = Objects.requireNonNull(local, "No local path").normalize().toAbsolutePath();
         if (shouldBeDir) {
             LinkOption[] options = IoUtils.getLinkOptions(true);
@@ -338,7 +340,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
                       this, header, path, preserve, time, bufferSize);
         }
 
-        receiveStream(header, new LocalFileScpTargetStreamResolver(path, opener), time, preserve, bufferSize);
+        receiveStream(header, streamFactory.createScpTargetStreamResolver(path, opener), time, preserve, bufferSize);
     }
 
     public void receiveStream(String header, ScpTargetStreamResolver resolver, ScpTimestamp time, boolean preserve, int bufferSize) throws IOException {
@@ -558,7 +560,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
             log.debug("sendFile({})[preserve={},buffer-size={}] Sending file {}", this, preserve, bufferSize, path);
         }
 
-        sendStream(new LocalFileScpSourceStreamResolver(path, opener), preserve, bufferSize);
+        sendStream(streamFactory.createScpSourceStreamResolver(path, opener), preserve, bufferSize);
     }
 
     public void sendStream(ScpSourceStreamResolver resolver, boolean preserve, int bufferSize) throws IOException {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactory.java
new file mode 100644
index 0000000..0dce708
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.IOException;
+import java.nio.file.Path;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ScpStreamResolverFactory {
+    ScpSourceStreamResolver createScpSourceStreamResolver(Path path, ScpFileOpener opener) throws IOException;
+
+    ScpTargetStreamResolver createScpTargetStreamResolver(Path path, ScpFileOpener opener) throws IOException;
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactoryHolder.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactoryHolder.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactoryHolder.java
new file mode 100644
index 0000000..3483629
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpStreamResolverFactoryHolder.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ScpStreamResolverFactoryHolder {
+    ScpStreamResolverFactory getScpStreamResolverFactory();
+
+    void setScpStreamResolverFactory(ScpStreamResolverFactory factory);
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/common/scp/helpers/DefaultScpStreamResolverFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/helpers/DefaultScpStreamResolverFactory.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/helpers/DefaultScpStreamResolverFactory.java
new file mode 100644
index 0000000..60933e0
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/helpers/DefaultScpStreamResolverFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.helpers;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpSourceStreamResolver;
+import org.apache.sshd.common.scp.ScpStreamResolverFactory;
+import org.apache.sshd.common.scp.ScpTargetStreamResolver;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultScpStreamResolverFactory extends AbstractLoggingBean implements ScpStreamResolverFactory {
+    public static final DefaultScpStreamResolverFactory INSTANCE = new DefaultScpStreamResolverFactory();
+
+    public DefaultScpStreamResolverFactory() {
+        super();
+    }
+
+    @Override
+    public ScpSourceStreamResolver createScpSourceStreamResolver(Path path, ScpFileOpener opener) throws IOException {
+        return new LocalFileScpSourceStreamResolver(path, opener);
+    }
+
+    @Override
+    public ScpTargetStreamResolver createScpTargetStreamResolver(Path path, ScpFileOpener opener) throws IOException {
+        return new LocalFileScpTargetStreamResolver(path, opener);
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommand.java
index e59a331..3667aa1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommand.java
@@ -31,8 +31,10 @@ import org.apache.sshd.common.file.FileSystemAware;
 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.ScpStreamResolverFactory;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
+import org.apache.sshd.common.scp.helpers.DefaultScpStreamResolverFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.session.SessionHolder;
 import org.apache.sshd.common.util.GenericUtils;
@@ -61,7 +63,7 @@ public class ScpCommand
     protected final int sendBufferSize;
     protected final int receiveBufferSize;
     protected final ScpFileOpener opener;
-
+    protected final ScpStreamResolverFactory streamFactory;
     protected boolean optR;
     protected boolean optT;
     protected boolean optF;
@@ -91,6 +93,7 @@ public class ScpCommand
      * @param sendSize        Size (in bytes) of buffer to use when sending files
      * @param receiveSize     Size (in bytes) of buffer to use when receiving files
      * @param fileOpener      The {@link ScpFileOpener} - if {@code null} then {@link DefaultScpFileOpener} is used
+     * @param factory         The {@link ScpStreamResolverFactory} - if {@code null} then {@link DefaultScpStreamResolverFactory} is used
      * @param eventListener   An {@link ScpTransferEventListener} - may be {@code null}
      * @see ThreadUtils#newSingleThreadExecutor(String)
      * @see ScpHelper#MIN_SEND_BUFFER_SIZE
@@ -99,7 +102,7 @@ public class ScpCommand
     public ScpCommand(String command,
             ExecutorService executorService, boolean shutdownOnExit,
             int sendSize, int receiveSize,
-            ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
+            ScpFileOpener fileOpener, ScpStreamResolverFactory factory, ScpTransferEventListener eventListener) {
         name = command;
 
         if (executorService == null) {
@@ -126,6 +129,7 @@ public class ScpCommand
         receiveBufferSize = receiveSize;
 
         opener = (fileOpener == null) ? DefaultScpFileOpener.INSTANCE : fileOpener;
+        streamFactory = (factory == null) ? DefaultScpStreamResolverFactory.INSTANCE : factory;
         listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
 
         if (log.isDebugEnabled()) {
@@ -269,7 +273,7 @@ public class ScpCommand
     public void run() {
         int exitValue = ScpHelper.OK;
         String exitMessage = null;
-        ScpHelper helper = new ScpHelper(getServerSession(), in, out, fileSystem, opener, listener);
+        ScpHelper helper = new ScpHelper(getServerSession(), in, out, fileSystem, opener, streamFactory, listener);
         try {
             if (optT) {
                 helper.receive(helper.resolveLocalPath(path), optR, optD, optP, receiveBufferSize);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
index 67549b5..b3b2d89 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/scp/ScpCommandFactory.java
@@ -25,6 +25,8 @@ 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.ScpStreamResolverFactory;
+import org.apache.sshd.common.scp.ScpStreamResolverFactoryHolder;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.util.EventListenerUtils;
 import org.apache.sshd.common.util.ObjectBuilder;
@@ -40,7 +42,12 @@ import org.apache.sshd.server.CommandFactory;
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  * @see ScpCommand
  */
-public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, Cloneable, ExecutorServiceConfigurer {
+public class ScpCommandFactory
+        implements ScpFileOpenerHolder,
+        ScpStreamResolverFactoryHolder,
+        CommandFactory,
+        Cloneable,
+        ExecutorServiceConfigurer {
     /**
      * A useful {@link ObjectBuilder} for {@link ScpCommandFactory}
      */
@@ -56,6 +63,11 @@ public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, C
             return this;
         }
 
+        public Builder withScpStreamResolverFactory(ScpStreamResolverFactory streamFactory) {
+            factory.setScpStreamResolverFactory(streamFactory);
+            return this;
+        }
+
         public Builder withDelegate(CommandFactory delegate) {
             factory.setDelegateCommandFactory(delegate);
             return this;
@@ -105,6 +117,7 @@ public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, C
     private ExecutorService executors;
     private boolean shutdownExecutor;
     private ScpFileOpener fileOpener;
+    private ScpStreamResolverFactory streamFactory;
     private int sendBufferSize = ScpHelper.MIN_SEND_BUFFER_SIZE;
     private int receiveBufferSize = ScpHelper.MIN_RECEIVE_BUFFER_SIZE;
     private Collection<ScpTransferEventListener> listeners = new CopyOnWriteArraySet<>();
@@ -124,6 +137,16 @@ public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, C
         this.fileOpener = fileOpener;
     }
 
+    @Override
+    public ScpStreamResolverFactory getScpStreamResolverFactory() {
+        return streamFactory;
+    }
+
+    @Override
+    public void setScpStreamResolverFactory(ScpStreamResolverFactory streamFactory) {
+        this.streamFactory = streamFactory;
+    }
+
     public CommandFactory getDelegateCommandFactory() {
         return delegate;
     }
@@ -242,7 +265,7 @@ public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, C
             return new ScpCommand(command,
                     getExecutorService(), isShutdownOnExit(),
                     getSendBufferSize(), getReceiveBufferSize(),
-                    getScpFileOpener(), listenerProxy);
+                    getScpFileOpener(), getScpStreamResolverFactory(), listenerProxy);
         }
 
         CommandFactory factory = getDelegateCommandFactory();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1cd204b7/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
index 42907d6..56ee962 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/scp/ScpTest.java
@@ -53,8 +53,10 @@ 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.ScpStreamResolverFactory;
 import org.apache.sshd.common.scp.ScpTransferEventListener;
 import org.apache.sshd.common.scp.helpers.DefaultScpFileOpener;
+import org.apache.sshd.common.scp.helpers.DefaultScpStreamResolverFactory;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.OsUtils;
@@ -768,8 +770,9 @@ public class ScpTest extends BaseTestSupport {
             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);
+                    int sendSize, int receiveSize, ScpFileOpener opener,
+                    ScpStreamResolverFactory factory, ScpTransferEventListener eventListener) {
+                super(command, executorService, shutdownOnExit, sendSize, receiveSize, opener, factory, eventListener);
             }
 
             @Override
@@ -808,7 +811,9 @@ public class ScpTest extends BaseTestSupport {
                 return new InternalScpCommand(command,
                         getExecutorService(), isShutdownOnExit(),
                         getSendBufferSize(), getReceiveBufferSize(),
-                        DefaultScpFileOpener.INSTANCE, ScpTransferEventListener.EMPTY);
+                        DefaultScpFileOpener.INSTANCE,
+                        DefaultScpStreamResolverFactory.INSTANCE,
+                        ScpTransferEventListener.EMPTY);
             }
         });