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 2016/02/18 15:36:29 UTC
mina-sshd git commit: [SSHD-649] Provide pluggable file data stream
opener for SCP command
Repository: mina-sshd
Updated Branches:
refs/heads/master 8628bd2d9 -> 4a8c81717
[SSHD-649] Provide pluggable file data stream opener for SCP command
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/4a8c8171
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/4a8c8171
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/4a8c8171
Branch: refs/heads/master
Commit: 4a8c81717829bc445bec87fabf51fea3951c0c84
Parents: 8628bd2
Author: Lyor Goldstein <ly...@gmail.com>
Authored: Thu Feb 18 16:37:10 2016 +0200
Committer: Lyor Goldstein <ly...@gmail.com>
Committed: Thu Feb 18 16:37:10 2016 +0200
----------------------------------------------------------------------
.../sshd/client/scp/DefaultScpClient.java | 22 +--
.../client/scp/DefaultScpStreamResolver.java | 4 +-
.../sshd/client/scp/ScpClientCreator.java | 103 ++++++++++++
.../client/session/AbstractClientSession.java | 29 +++-
.../sshd/client/session/ClientSession.java | 41 +----
.../scp/LocalFileScpSourceStreamResolver.java | 90 -----------
.../scp/LocalFileScpTargetStreamResolver.java | 150 ------------------
.../apache/sshd/common/scp/ScpFileOpener.java | 59 +++++++
.../org/apache/sshd/common/scp/ScpHelper.java | 28 ++--
.../common/scp/ScpSourceStreamResolver.java | 7 +-
.../common/scp/ScpTargetStreamResolver.java | 14 +-
.../common/scp/impl/DefaultScpFileOpener.java | 63 ++++++++
.../impl/LocalFileScpSourceStreamResolver.java | 97 ++++++++++++
.../impl/LocalFileScpTargetStreamResolver.java | 158 +++++++++++++++++++
.../org/apache/sshd/server/scp/ScpCommand.java | 31 +++-
.../sshd/server/scp/ScpCommandFactory.java | 24 ++-
.../org/apache/sshd/client/scp/ScpTest.java | 103 +++++++++++-
17 files changed, 704 insertions(+), 319 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 c3b9d02..5c2fd74 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
@@ -42,10 +42,12 @@ import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.file.util.MockFileSystem;
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.ScpTimestamp;
import org.apache.sshd.common.scp.ScpTransferEventListener;
+import org.apache.sshd.common.scp.impl.DefaultScpFileOpener;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.NoCloseInputStream;
@@ -55,15 +57,13 @@ import org.apache.sshd.common.util.io.NoCloseInputStream;
*/
public class DefaultScpClient extends AbstractScpClient {
- private final ClientSession clientSession;
- private final ScpTransferEventListener listener;
+ protected final ClientSession clientSession;
+ protected final ScpFileOpener opener;
+ protected final ScpTransferEventListener listener;
- public DefaultScpClient(ClientSession clientSession) {
- this(clientSession, ScpTransferEventListener.EMPTY);
- }
-
- public DefaultScpClient(ClientSession clientSession, ScpTransferEventListener eventListener) {
+ public DefaultScpClient(ClientSession clientSession, ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
this.clientSession = ValidateUtils.checkNotNull(clientSession, "No client session");
+ this.opener = (fileOpener == null) ? DefaultScpFileOpener.INSTANCE : fileOpener;
this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
}
@@ -80,7 +80,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), listener);
+ ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, listener);
helper.receiveFileStream(local, ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE);
handleCommandExitStatus(cmd, channel);
} finally {
@@ -95,7 +95,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, listener);
+ ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, listener);
helper.receive(local,
options.contains(Option.Recursive),
options.contains(Option.TargetIsDirectory),
@@ -119,7 +119,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), listener);
+ ScpHelper helper = new ScpHelper(session, invOut, invIn, new MockFileSystem(remote), opener, listener);
final Path mockPath = new MockPath(remote);
helper.sendStream(new DefaultScpStreamResolver(name, mockPath, perms, time, size, local, cmd),
time != null, ScpHelper.DEFAULT_SEND_BUFFER_SIZE);
@@ -147,7 +147,7 @@ public class DefaultScpClient extends AbstractScpClient {
try (InputStream invOut = channel.getInvertedOut();
OutputStream invIn = channel.getInvertedIn()) {
- ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, listener);
+ ScpHelper helper = new ScpHelper(session, invOut, invIn, fs, opener, listener);
executor.execute(helper, local, options);
} finally {
try {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpStreamResolver.java
index 4ae22fb..e6362b8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpStreamResolver.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpStreamResolver.java
@@ -20,12 +20,14 @@ package org.apache.sshd.client.scp;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
import org.apache.sshd.common.scp.ScpSourceStreamResolver;
import org.apache.sshd.common.scp.ScpTimestamp;
+import org.apache.sshd.common.session.Session;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
@@ -75,7 +77,7 @@ public class DefaultScpStreamResolver implements ScpSourceStreamResolver {
}
@Override
- public InputStream resolveSourceStream() throws IOException {
+ public InputStream resolveSourceStream(Session session, OpenOption... options) throws IOException {
return local;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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
new file mode 100644
index 0000000..cfe598e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/ScpClientCreator.java
@@ -0,0 +1,103 @@
+/*
+ * 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 org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpTransferEventListener;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ScpClientCreator {
+ /**
+ * 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
+ * @see #setScpFileOpener(ScpFileOpener)
+ * @see #setScpTransferEventListener(ScpTransferEventListener)
+ */
+ ScpClient createScpClient();
+
+ /**
+ * Create an SCP client from this session.
+ *
+ * @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(ScpTransferEventListener listener);
+
+ /**
+ * Create an SCP client from this session.
+ *
+ * @param opener The {@link ScpFileOpener} to use to control how local files
+ * 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)}
+ * @return An {@link ScpClient} instance
+ */
+ ScpClient createScpClient(ScpFileOpener opener);
+
+ /**
+ * Create an SCP client from this session.
+ *
+ * @param opener The {@link ScpFileOpener} to use to control how local files
+ * 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 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);
+
+ /**
+ * @return The last {@link ScpFileOpener} set via call
+ * to {@link #setScpFileOpener(ScpFileOpener)}
+ */
+ ScpFileOpener getScpFileOpener();
+
+ /**
+ * @param opener The default {@link ScpFileOpener} to use - if {@code null}
+ * then a default opener is used
+ * @see #createScpClient(ScpFileOpener)
+ * @see #createScpClient(ScpFileOpener, ScpTransferEventListener)
+ */
+ void setScpFileOpener(ScpFileOpener opener);
+
+ /**
+ * @return The last {@link ScpTransferEventListener} set via
+ * {@link #setScpTransferEventListener(ScpTransferEventListener)}
+ */
+ ScpTransferEventListener getScpTransferEventListener();
+
+ /**
+ * @param listener A default {@link ScpTransferEventListener} that can be used
+ * to receive information about the SCP operations - may be {@code null}
+ * to indicate no more events are required
+ * @see #createScpClient(ScpTransferEventListener)
+ */
+ void setScpTransferEventListener(ScpTransferEventListener listener);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 195d5ef..25e441c 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
@@ -50,6 +50,7 @@ import org.apache.sshd.common.channel.Channel;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.forward.TcpipForwarder;
import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.scp.ScpFileOpener;
import org.apache.sshd.common.scp.ScpTransferEventListener;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.session.ConnectionService;
@@ -68,6 +69,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
private PasswordIdentityProvider passwordIdentityProvider;
private List<NamedFactory<UserAuth>> userAuthFactories;
private ScpTransferEventListener scpListener;
+ private ScpFileOpener scpOpener;
private SocketAddress connectAddress;
protected AbstractClientSession(ClientFactoryManager factoryManager, IoSession ioSession) {
@@ -245,6 +247,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C
}
@Override
+ public ScpFileOpener getScpFileOpener() {
+ return scpOpener;
+ }
+
+ @Override
+ public void setScpFileOpener(ScpFileOpener opener) {
+ scpOpener = opener;
+ }
+
+ @Override
public ScpTransferEventListener getScpTransferEventListener() {
return scpListener;
}
@@ -254,17 +266,26 @@ public abstract class AbstractClientSession extends AbstractSession implements C
scpListener = listener;
}
- @Override
+ @Override // TODO make this a default method in JDK-8
public ScpClient createScpClient() {
- return createScpClient(getScpTransferEventListener());
+ return createScpClient(getScpFileOpener(), getScpTransferEventListener());
}
- @Override
+ @Override // TODO make this a default method in JDK-8
public ScpClient createScpClient(ScpTransferEventListener listener) {
- return new DefaultScpClient(this, listener);
+ return createScpClient(getScpFileOpener(), listener);
}
@Override // TODO make this a default method in JDK-8
+ public ScpClient createScpClient(ScpFileOpener opener) {
+ return createScpClient(opener, getScpTransferEventListener());
+ }
+
+ @Override
+ public ScpClient createScpClient(ScpFileOpener opener, ScpTransferEventListener listener) {
+ return new DefaultScpClient(this, opener, listener);
+ }
+ @Override // TODO make this a default method in JDK-8
public SftpClient createSftpClient() throws IOException {
return createSftpClient(SftpVersionSelector.CURRENT);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
index db3f10f..d9633b0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
@@ -33,11 +33,10 @@ import org.apache.sshd.client.channel.ChannelShell;
import org.apache.sshd.client.channel.ChannelSubsystem;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.future.AuthFuture;
-import org.apache.sshd.client.scp.ScpClient;
+import org.apache.sshd.client.scp.ScpClientCreator;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
import org.apache.sshd.common.future.KeyExchangeFuture;
-import org.apache.sshd.common.scp.ScpTransferEventListener;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.net.SshdSocketAddress;
@@ -69,7 +68,9 @@ import org.apache.sshd.common.util.net.SshdSocketAddress;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public interface ClientSession extends Session, ClientAuthenticationManager {
+public interface ClientSession
+ extends Session, ScpClientCreator,
+ ClientAuthenticationManager {
enum ClientSessionEvent {
TIMEOUT,
CLOSED,
@@ -158,40 +159,6 @@ public interface ClientSession extends Session, ClientAuthenticationManager {
ChannelDirectTcpip createDirectTcpipChannel(SshdSocketAddress local, SshdSocketAddress remote) throws IOException;
/**
- * Create an SCP client from this session.
- *
- * @return An {@link ScpClient} instance. <B>Note:</B> uses the currently
- * registered {@link ScpTransferEventListener} if any
- * @see #setScpTransferEventListener(ScpTransferEventListener)
- */
- ScpClient createScpClient();
-
- /**
- * Create an SCP client from this session.
- *
- * @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(ScpTransferEventListener listener);
-
- /**
- * @return The last {@link ScpTransferEventListener} set via
- * {@link #setScpTransferEventListener(ScpTransferEventListener)}
- */
- ScpTransferEventListener getScpTransferEventListener();
-
- /**
- * @param listener A default {@link ScpTransferEventListener} that can be used
- * to receive information about the SCP operations - may be {@code null}
- * to indicate no more events are required
- * @see #createScpClient(ScpTransferEventListener)
- */
- void setScpTransferEventListener(ScpTransferEventListener listener);
-
- /**
* Create an SFTP client from this session.
*
* @return The created {@link SftpClient}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
deleted file mode 100644
index 7e7d5f2..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpSourceStreamResolver.java
+++ /dev/null
@@ -1,90 +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.common.scp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.util.Collection;
-import java.util.Set;
-
-import org.apache.sshd.common.util.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class LocalFileScpSourceStreamResolver extends AbstractLoggingBean implements ScpSourceStreamResolver {
- private final Path path;
- private final Path name;
- private final Set<PosixFilePermission> perms;
- private final long size;
- private final ScpTimestamp time;
-
- public LocalFileScpSourceStreamResolver(Path path) throws IOException {
- this.path = ValidateUtils.checkNotNull(path, "No path specified");
- this.name = path.getFileName();
- this.perms = IoUtils.getPermissions(path);
-
- BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
- this.size = basic.size();
- this.time = new ScpTimestamp(basic.lastModifiedTime().toMillis(), basic.lastAccessTime().toMillis());
- }
-
- @Override
- public String getFileName() throws IOException {
- return name.toString();
- }
-
- @Override
- public Collection<PosixFilePermission> getPermissions() throws IOException {
- return perms;
- }
-
- @Override
- public ScpTimestamp getTimestamp() throws IOException {
- return time;
- }
-
- @Override
- public long getSize() throws IOException {
- return size;
- }
-
- @Override
- public Path getEventListenerFilePath() {
- return path;
- }
-
- @Override
- public InputStream resolveSourceStream() throws IOException {
- return Files.newInputStream(getEventListenerFilePath());
- }
-
- @Override
- public String toString() {
- return String.valueOf(getEventListenerFilePath());
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
deleted file mode 100644
index f6b86d9..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/LocalFileScpTargetStreamResolver.java
+++ /dev/null
@@ -1,150 +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.common.scp;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.PosixFilePermission;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.common.util.logging.AbstractLoggingBean;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class LocalFileScpTargetStreamResolver extends AbstractLoggingBean implements ScpTargetStreamResolver {
- private final Path path;
- private final Boolean status;
- private Path file;
-
- public LocalFileScpTargetStreamResolver(Path path) throws IOException {
- LinkOption[] options = IoUtils.getLinkOptions(false);
- this.status = IoUtils.checkFileExists(path, options);
- if (status == null) {
- throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
- }
-
- this.path = path;
- }
-
- @Override
- public OutputStream resolveTargetStream(String name, long length, Set<PosixFilePermission> perms) throws IOException {
- if (file != null) {
- throw new StreamCorruptedException("resolveTargetStream(" + name + ")[" + perms + "] already resolved: " + file);
- }
-
- LinkOption[] options = IoUtils.getLinkOptions(false);
- if (status && Files.isDirectory(path, options)) {
- String localName = name.replace('/', File.separatorChar); // in case we are running on Windows
- file = path.resolve(localName);
- } else if (status && Files.isRegularFile(path, options)) {
- file = path;
- } else if (!status) {
- Path parent = path.getParent();
-
- Boolean parentStatus = IoUtils.checkFileExists(parent, options);
- if (parentStatus == null) {
- throw new AccessDeniedException("Receive file parent (" + parent + ") existence status cannot be determined for " + path);
- }
-
- if (parentStatus && Files.isDirectory(parent, options)) {
- file = path;
- }
- }
-
- if (file == null) {
- throw new IOException("Can not write to " + path);
- }
-
- Boolean fileStatus = IoUtils.checkFileExists(file, options);
- if (fileStatus == null) {
- throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
- }
-
- if (fileStatus) {
- if (Files.isDirectory(file, options)) {
- throw new IOException("File is a directory: " + file);
- }
-
- if (!Files.isWritable(file)) {
- throw new IOException("Can not write to file: " + file);
- }
- }
-
- if (log.isTraceEnabled()) {
- log.trace("resolveTargetStream(" + name + "): " + file);
- }
-
- return Files.newOutputStream(file);
- }
-
- @Override
- public Path getEventListenerFilePath() {
- if (file == null) {
- return path;
- } else {
- return file;
- }
- }
-
- @Override
- public void postProcessReceivedData(String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
- if (file == null) {
- throw new StreamCorruptedException("postProcessReceivedData(" + name + ")[" + perms + "] No currently resolved data");
- }
-
- if (preserve) {
- updateFileProperties(name, file, perms, time);
- }
- }
-
- protected void updateFileProperties(String name, Path path, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
- if (log.isTraceEnabled()) {
- log.trace("updateFileProperties(" + name + ")[" + path + "] permissions: " + perms);
- }
- IoUtils.setPermissions(path, perms);
-
- if (time != null) {
- BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
- FileTime lastModified = FileTime.from(time.getLastModifiedTime(), TimeUnit.MILLISECONDS);
- FileTime lastAccess = FileTime.from(time.getLastAccessTime(), TimeUnit.MILLISECONDS);
- if (log.isTraceEnabled()) {
- log.trace("updateFileProperties(" + name + ")[" + path + "] last-modified=" + lastModified + ", last-access=" + lastAccess);
- }
-
- view.setTimes(lastModified, lastAccess, null);
- }
- }
-
- @Override
- public String toString() {
- return String.valueOf(getEventListenerFilePath());
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpFileOpener.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpFileOpener.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpFileOpener.java
new file mode 100644
index 0000000..e2db9b8
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpFileOpener.java
@@ -0,0 +1,59 @@
+/*
+ * 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.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+
+import org.apache.sshd.common.session.Session;
+
+/**
+ * Plug-in mechanism for users to intervene in the SCP process - e.g.,
+ * apply some kind of traffic shaping mechanism, display upload/download
+ * progress, etc...
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface ScpFileOpener {
+ /**
+ * Create an input stream to read from a file
+ *
+ * @param session The {@link Session} requesting the access
+ * @param file The requested local file {@link Path}
+ * @param options The {@link OpenOption}s - may be {@code null}/empty
+ * @return The open {@link InputStream} never {@code null}
+ * @throws IOException If failed to open the file
+ */
+ InputStream openRead(Session session, Path file, OpenOption... options) throws IOException;
+
+ /**
+ * Create an input stream to read from a file
+ *
+ * @param session The {@link Session} requesting the access
+ * @param file The requested local file {@link Path}
+ * @param options The {@link OpenOption}s - may be {@code null}/empty
+ * @return The open {@link OutputStream} never {@code null}
+ * @throws IOException If failed to open the file
+ */
+ OutputStream openWrite(Session session, Path file, OpenOption... options) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 9a0f806..f415db3 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
@@ -32,6 +32,7 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
@@ -45,6 +46,9 @@ import java.util.concurrent.TimeUnit;
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.impl.DefaultScpFileOpener;
+import org.apache.sshd.common.scp.impl.LocalFileScpSourceStreamResolver;
+import org.apache.sshd.common.scp.impl.LocalFileScpTargetStreamResolver;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.session.SessionHolder;
import org.apache.sshd.common.util.GenericUtils;
@@ -92,24 +96,27 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
public static final int S_IWOTH = 0000002;
public static final int S_IXOTH = 0000001;
- protected final FileSystem fileSystem;
protected final InputStream in;
protected final OutputStream out;
+ protected final FileSystem fileSystem;
+ protected final ScpFileOpener opener;
protected final ScpTransferEventListener listener;
- private final Session session;
+ private final Session sessionInstance;
- public ScpHelper(Session session, InputStream in, OutputStream out, FileSystem fileSystem, ScpTransferEventListener eventListener) {
- this.session = ValidateUtils.checkNotNull(session, "No session");
+ public ScpHelper(Session session, InputStream in, OutputStream out,
+ FileSystem fileSystem, ScpFileOpener opener, ScpTransferEventListener eventListener) {
+ this.sessionInstance = ValidateUtils.checkNotNull(session, "No session");
this.in = ValidateUtils.checkNotNull(in, "No input stream");
this.out = ValidateUtils.checkNotNull(out, "No output stream");
this.fileSystem = fileSystem;
+ this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
this.listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
}
@Override
public Session getSession() {
- return session;
+ return sessionInstance;
}
public void receiveFileStream(final OutputStream local, final int bufferSize) throws IOException {
@@ -124,7 +131,8 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
receiveStream(line, new ScpTargetStreamResolver() {
@SuppressWarnings("synthetic-access")
@Override
- public OutputStream resolveTargetStream(String name, long length, Set<PosixFilePermission> perms) throws IOException {
+ public OutputStream resolveTargetStream(Session session, String name, long length,
+ Set<PosixFilePermission> perms, OpenOption... options) throws IOException {
if (log.isDebugEnabled()) {
log.debug("resolveTargetStream({}) name={}, perms={}, len={} - started local stream download",
ScpHelper.this, name, perms, length);
@@ -331,7 +339,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
this, header, path, preserve, time, bufferSize);
}
- receiveStream(header, new LocalFileScpTargetStreamResolver(path), time, preserve, bufferSize);
+ receiveStream(header, new LocalFileScpTargetStreamResolver(path, opener), time, preserve, bufferSize);
}
public void receiveStream(String header, ScpTargetStreamResolver resolver, ScpTimestamp time, boolean preserve, int bufferSize) throws IOException {
@@ -370,7 +378,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
try (
InputStream is = new LimitInputStream(this.in, length);
- OutputStream os = resolver.resolveTargetStream(name, length, perms)
+ OutputStream os = resolver.resolveTargetStream(getSession(), name, length, perms)
) {
ack();
@@ -551,7 +559,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), preserve, bufferSize);
+ sendStream(new LocalFileScpSourceStreamResolver(path, opener), preserve, bufferSize);
}
public void sendStream(ScpSourceStreamResolver resolver, boolean preserve, int bufferSize) throws IOException {
@@ -615,7 +623,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
}
validateAckReplyCode(cmd, resolver, readyCode, false);
- try (InputStream in = resolver.resolveSourceStream()) {
+ try (InputStream in = resolver.resolveSourceStream(getSession())) {
Path path = resolver.getEventListenerFilePath();
try {
listener.startFileEvent(FileOperation.SEND, path, fileSize, perms);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpSourceStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpSourceStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpSourceStreamResolver.java
index 73af189..feeecbc 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpSourceStreamResolver.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpSourceStreamResolver.java
@@ -21,10 +21,13 @@ package org.apache.sshd.common.scp;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collection;
+import org.apache.sshd.common.session.Session;
+
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@@ -61,8 +64,10 @@ public interface ScpSourceStreamResolver {
long getSize() throws IOException;
/**
+ * @param session The {@link Session} through which file is transmitted
+ * @param options The {@link OpenOption}s may be {@code null}/empty
* @return The {@link InputStream} containing the data to be uploaded
* @throws IOException If failed to create the stream
*/
- InputStream resolveSourceStream() throws IOException;
+ InputStream resolveSourceStream(Session session, OpenOption... options) throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTargetStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTargetStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTargetStreamResolver.java
index 8b95b4f..9a70302 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTargetStreamResolver.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpTargetStreamResolver.java
@@ -21,10 +21,13 @@ package org.apache.sshd.common.scp;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
+import org.apache.sshd.common.session.Session;
+
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@@ -33,13 +36,16 @@ public interface ScpTargetStreamResolver {
* Called when receiving a file in order to obtain an output stream
* for the incoming data
*
- * @param name File name as received from remote site
- * @param length Number of bytes expected to receive
- * @param perms The {@link Set} of {@link PosixFilePermission} expected
+ * @param session The associated {@link Session}
+ * @param name File name as received from remote site
+ * @param length Number of bytes expected to receive
+ * @param perms The {@link Set} of {@link PosixFilePermission} expected
+ * @param options The {@link OpenOption}s to use - may be {@code null}/empty
* @return The {@link OutputStream} to write the incoming data
* @throws IOException If failed to create the stream
*/
- OutputStream resolveTargetStream(String name, long length, Set<PosixFilePermission> perms) throws IOException;
+ OutputStream resolveTargetStream(Session session, String name, long length,
+ Set<PosixFilePermission> perms, OpenOption... options) throws IOException;
/**
* @return The {@link Path} to use when invoking the {@link ScpTransferEventListener}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/DefaultScpFileOpener.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/DefaultScpFileOpener.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/DefaultScpFileOpener.java
new file mode 100644
index 0000000..0545185
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/DefaultScpFileOpener.java
@@ -0,0 +1,63 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.Arrays;
+
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class DefaultScpFileOpener extends AbstractLoggingBean implements ScpFileOpener {
+ public static final DefaultScpFileOpener INSTANCE = new DefaultScpFileOpener();
+
+ public DefaultScpFileOpener() {
+ super();
+ }
+
+ @Override
+ public InputStream openRead(Session session, Path file, OpenOption... options) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("openRead({}) file={}, options={}",
+ session, file, Arrays.toString(options));
+ }
+
+ return Files.newInputStream(file, options);
+ }
+
+ @Override
+ public OutputStream openWrite(Session session, Path file, OpenOption... options) throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("openWrite({}) file={}, options={}",
+ session, file, Arrays.toString(options));
+ }
+
+ return Files.newOutputStream(file, options);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpSourceStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpSourceStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpSourceStreamResolver.java
new file mode 100644
index 0000000..9a3a5c3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpSourceStreamResolver.java
@@ -0,0 +1,97 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpSourceStreamResolver;
+import org.apache.sshd.common.scp.ScpTimestamp;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LocalFileScpSourceStreamResolver extends AbstractLoggingBean implements ScpSourceStreamResolver {
+ protected final Path path;
+ protected final ScpFileOpener opener;
+ protected final Path name;
+ protected final Set<PosixFilePermission> perms;
+ protected final long size;
+ protected final ScpTimestamp time;
+
+ public LocalFileScpSourceStreamResolver(Path path, ScpFileOpener opener) throws IOException {
+ this.path = ValidateUtils.checkNotNull(path, "No path specified");
+ this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
+ this.name = path.getFileName();
+ this.perms = IoUtils.getPermissions(path);
+
+ BasicFileAttributes basic = Files.getFileAttributeView(path, BasicFileAttributeView.class).readAttributes();
+ this.size = basic.size();
+ this.time = new ScpTimestamp(basic.lastModifiedTime().toMillis(), basic.lastAccessTime().toMillis());
+ }
+
+ @Override
+ public String getFileName() throws IOException {
+ return name.toString();
+ }
+
+ @Override
+ public Collection<PosixFilePermission> getPermissions() throws IOException {
+ return perms;
+ }
+
+ @Override
+ public ScpTimestamp getTimestamp() throws IOException {
+ return time;
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return size;
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ return path;
+ }
+
+ @Override
+ public InputStream resolveSourceStream(Session session, OpenOption... options) throws IOException {
+ return opener.openRead(session, getEventListenerFilePath(), options);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getEventListenerFilePath());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpTargetStreamResolver.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpTargetStreamResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpTargetStreamResolver.java
new file mode 100644
index 0000000..7d1b78b
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/impl/LocalFileScpTargetStreamResolver.java
@@ -0,0 +1,158 @@
+/*
+ * 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.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.common.scp.ScpFileOpener;
+import org.apache.sshd.common.scp.ScpTargetStreamResolver;
+import org.apache.sshd.common.scp.ScpTimestamp;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LocalFileScpTargetStreamResolver extends AbstractLoggingBean implements ScpTargetStreamResolver {
+ protected final Path path;
+ protected final ScpFileOpener opener;
+ protected final Boolean status;
+ private Path file;
+
+ public LocalFileScpTargetStreamResolver(Path path, ScpFileOpener opener) throws IOException {
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(false);
+ this.status = IoUtils.checkFileExists(path, linkOptions);
+ if (status == null) {
+ throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
+ }
+
+ this.path = path;
+ this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
+ }
+
+ @Override
+ public OutputStream resolveTargetStream(Session session, String name, long length,
+ Set<PosixFilePermission> perms, OpenOption... options) throws IOException {
+ if (file != null) {
+ throw new StreamCorruptedException("resolveTargetStream(" + name + ")[" + perms + "] already resolved: " + file);
+ }
+
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(false);
+ if (status && Files.isDirectory(path, linkOptions)) {
+ String localName = name.replace('/', File.separatorChar); // in case we are running on Windows
+ file = path.resolve(localName);
+ } else if (status && Files.isRegularFile(path, linkOptions)) {
+ file = path;
+ } else if (!status) {
+ Path parent = path.getParent();
+
+ Boolean parentStatus = IoUtils.checkFileExists(parent, linkOptions);
+ if (parentStatus == null) {
+ throw new AccessDeniedException("Receive file parent (" + parent + ") existence status cannot be determined for " + path);
+ }
+
+ if (parentStatus && Files.isDirectory(parent, linkOptions)) {
+ file = path;
+ }
+ }
+
+ if (file == null) {
+ throw new IOException("Can not write to " + path);
+ }
+
+ Boolean fileStatus = IoUtils.checkFileExists(file, linkOptions);
+ if (fileStatus == null) {
+ throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
+ }
+
+ if (fileStatus) {
+ if (Files.isDirectory(file, linkOptions)) {
+ throw new IOException("File is a directory: " + file);
+ }
+
+ if (!Files.isWritable(file)) {
+ throw new IOException("Can not write to file: " + file);
+ }
+ }
+
+ if (log.isTraceEnabled()) {
+ log.trace("resolveTargetStream(" + name + "): " + file);
+ }
+
+ return opener.openWrite(session, file, options);
+ }
+
+ @Override
+ public Path getEventListenerFilePath() {
+ if (file == null) {
+ return path;
+ } else {
+ return file;
+ }
+ }
+
+ @Override
+ public void postProcessReceivedData(String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (file == null) {
+ throw new StreamCorruptedException("postProcessReceivedData(" + name + ")[" + perms + "] No currently resolved data");
+ }
+
+ if (preserve) {
+ updateFileProperties(name, file, perms, time);
+ }
+ }
+
+ protected void updateFileProperties(String name, Path path, Set<PosixFilePermission> perms, ScpTimestamp time) throws IOException {
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + name + ")[" + path + "] permissions: " + perms);
+ }
+ IoUtils.setPermissions(path, perms);
+
+ if (time != null) {
+ BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
+ FileTime lastModified = FileTime.from(time.getLastModifiedTime(), TimeUnit.MILLISECONDS);
+ FileTime lastAccess = FileTime.from(time.getLastAccessTime(), TimeUnit.MILLISECONDS);
+ if (log.isTraceEnabled()) {
+ log.trace("updateFileProperties(" + name + ")[" + path + "] last-modified=" + lastModified + ", last-access=" + lastAccess);
+ }
+
+ view.setTimes(lastModified, lastAccess, null);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getEventListenerFilePath());
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 2a48a9a..9a197f8 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
@@ -29,8 +29,12 @@ import java.util.concurrent.Future;
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.ScpTransferEventListener;
+import org.apache.sshd.common.scp.impl.DefaultScpFileOpener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionHolder;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.common.util.threads.ThreadUtils;
@@ -50,9 +54,14 @@ import org.apache.sshd.server.session.ServerSessionHolder;
*/
public class ScpCommand
extends AbstractLoggingBean
- implements Command, Runnable, FileSystemAware, SessionAware, ServerSessionHolder {
+ implements Command, Runnable, FileSystemAware, SessionAware,
+ SessionHolder<Session>, ServerSessionHolder {
+
+ protected final String name;
+ protected final int sendBufferSize;
+ protected final int receiveBufferSize;
+ protected final ScpFileOpener opener;
- protected String name;
protected boolean optR;
protected boolean optT;
protected boolean optF;
@@ -68,8 +77,6 @@ public class ScpCommand
protected ExecutorService executors;
protected boolean shutdownExecutor;
protected Future<?> pendingFuture;
- protected int sendBufferSize;
- protected int receiveBufferSize;
protected ScpTransferEventListener listener;
protected ServerSession serverSession;
@@ -83,12 +90,16 @@ public class ScpCommand
* service, which will be shutdown regardless
* @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 eventListener An {@link ScpTransferEventListener} - may be {@code null}
* @see ThreadUtils#newSingleThreadExecutor(String)
* @see ScpHelper#MIN_SEND_BUFFER_SIZE
* @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE
*/
- public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize, ScpTransferEventListener eventListener) {
+ public ScpCommand(String command,
+ ExecutorService executorService, boolean shutdownOnExit,
+ int sendSize, int receiveSize,
+ ScpFileOpener fileOpener, ScpTransferEventListener eventListener) {
name = command;
if (executorService == null) {
@@ -114,6 +125,7 @@ public class ScpCommand
}
receiveBufferSize = receiveSize;
+ opener = (fileOpener == null) ? DefaultScpFileOpener.INSTANCE : fileOpener;
listener = (eventListener == null) ? ScpTransferEventListener.EMPTY : eventListener;
if (log.isDebugEnabled()) {
@@ -169,6 +181,11 @@ public class ScpCommand
}
@Override
+ public Session getSession() {
+ return getServerSession();
+ }
+
+ @Override
public ServerSession getServerSession() {
return serverSession;
}
@@ -252,7 +269,7 @@ public class ScpCommand
public void run() {
int exitValue = ScpHelper.OK;
String exitMessage = null;
- ScpHelper helper = new ScpHelper(getServerSession(), in, out, fileSystem, listener);
+ ScpHelper helper = new ScpHelper(getServerSession(), in, out, fileSystem, opener, listener);
try {
if (optT) {
helper.receive(helper.resolveLocalPath(path), optR, optD, optP, receiveBufferSize);
@@ -312,6 +329,6 @@ public class ScpCommand
@Override
public String toString() {
- return name;
+ return getClass().getSimpleName() + "(" + getSession() + ") " + name;
}
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 1ffde01..1f52ea4 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
@@ -22,6 +22,7 @@ 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.ScpHelper;
import org.apache.sshd.common.scp.ScpTransferEventListener;
import org.apache.sshd.common.util.EventListenerUtils;
@@ -49,6 +50,11 @@ public class ScpCommandFactory implements CommandFactory, Cloneable, ExecutorSer
super();
}
+ public Builder withFileOpener(ScpFileOpener opener) {
+ factory.setFileOpener(opener);
+ return this;
+ }
+
public Builder withDelegate(CommandFactory delegate) {
factory.setDelegateCommandFactory(delegate);
return this;
@@ -97,6 +103,7 @@ public class ScpCommandFactory implements CommandFactory, Cloneable, ExecutorSer
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<>();
@@ -106,6 +113,18 @@ public class ScpCommandFactory implements CommandFactory, Cloneable, ExecutorSer
listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), listeners);
}
+ public ScpFileOpener getFileOpener() {
+ return fileOpener;
+ }
+
+ /**
+ * @param fileOpener The {@link ScpFileOpener} to use - if {@code null}.
+ * the a default opener is used
+ */
+ public void setFileOpener(ScpFileOpener fileOpener) {
+ this.fileOpener = fileOpener;
+ }
+
public CommandFactory getDelegateCommandFactory() {
return delegate;
}
@@ -226,7 +245,10 @@ public class ScpCommandFactory implements CommandFactory, Cloneable, ExecutorSer
@Override
public Command createCommand(String command) {
if (command.startsWith(ScpHelper.SCP_COMMAND_PREFIX)) {
- return new ScpCommand(command, getExecutorService(), isShutdownOnExit(), getSendBufferSize(), getReceiveBufferSize(), listenerProxy);
+ return new ScpCommand(command,
+ getExecutorService(), isShutdownOnExit(),
+ getSendBufferSize(), getReceiveBufferSize(),
+ getFileOpener(), listenerProxy);
}
CommandFactory factory = getDelegateCommandFactory();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4a8c8171/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 9f110ea..054ace8 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
@@ -26,6 +26,7 @@ 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;
@@ -36,6 +37,7 @@ 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 org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
@@ -45,8 +47,11 @@ 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.impl.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;
@@ -728,6 +733,95 @@ public class ScpTest extends BaseTestSupport {
}
}
+ @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();
+ TrackingFileOpener serverOpener = new TrackingFileOpener();
+ factory.setFileOpener(serverOpener);
+
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ TrackingFileOpener clientOpener = new TrackingFileOpener();
+ ScpClient scp = session.createScpClient(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(5L));
+
+ 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(5L));
+
+ 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 {
+ client.stop();
+ }
+ }
+ }
+
@Test // see SSHD-628
public void testScpExitStatusPropagation() throws Exception {
final int TEST_EXIT_VALUE = 7365;
@@ -735,8 +829,8 @@ public class ScpTest extends BaseTestSupport {
private ExitCallback delegate;
public InternalScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit,
- int sendSize, int receiveSize, ScpTransferEventListener eventListener) {
- super(command, executorService, shutdownOnExit, sendSize, receiveSize, eventListener);
+ int sendSize, int receiveSize, ScpFileOpener opener, ScpTransferEventListener eventListener) {
+ super(command, executorService, shutdownOnExit, sendSize, receiveSize, opener, eventListener);
}
@Override
@@ -770,7 +864,10 @@ public class ScpTest extends BaseTestSupport {
@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(), ScpTransferEventListener.EMPTY);
+ return new InternalScpCommand(command,
+ getExecutorService(), isShutdownOnExit(),
+ getSendBufferSize(), getReceiveBufferSize(),
+ DefaultScpFileOpener.INSTANCE, ScpTransferEventListener.EMPTY);
}
});