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 2015/07/05 15:08:07 UTC
[2/2] mina-sshd git commit: [SSHD-521] Expose SFTP extensions access
via interfaces for the client
[SSHD-521] Expose SFTP extensions access via interfaces for the client
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/f8a0e812
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/f8a0e812
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/f8a0e812
Branch: refs/heads/master
Commit: f8a0e81292c14aec501be1d6fdabcc0919f0fd17
Parents: ec3f287
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Sun Jul 5 16:07:48 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Sun Jul 5 16:07:48 2015 +0300
----------------------------------------------------------------------
.../sshd/client/subsystem/SubsystemClient.java | 6 +-
.../subsystem/sftp/AbstractSftpClient.java | 42 ++++-
.../subsystem/sftp/DefaultSftpClient.java | 13 +-
.../client/subsystem/sftp/RawSftpClient.java | 44 +++++
.../sshd/client/subsystem/sftp/SftpClient.java | 19 ++
.../client/subsystem/sftp/SftpFileSystem.java | 33 ++++
.../extensions/BuiltinSftpClientExtensions.java | 112 +++++++++++
.../sftp/extensions/CopyFileExtension.java | 36 ++++
.../sftp/extensions/MD5FileExtension.java | 39 ++++
.../sftp/extensions/MD5HandleExtension.java | 42 +++++
.../sftp/extensions/SftpClientExtension.java | 34 ++++
.../extensions/SftpClientExtensionFactory.java | 35 ++++
.../impl/AbstractMD5HashExtension.java | 74 ++++++++
.../impl/AbstractSftpClientExtension.java | 132 +++++++++++++
.../extensions/impl/CopyFileExtensionImpl.java | 53 ++++++
.../extensions/impl/MD5FileExtensionImpl.java | 42 +++++
.../extensions/impl/MD5HandleExtensionImpl.java | 43 +++++
.../subsystem/sftp/extensions/ParserUtils.java | 24 +++
.../server/subsystem/sftp/SftpSubsystem.java | 54 ++++--
.../sshd/client/subsystem/sftp/SftpTest.java | 185 ++++++++++++++++++-
.../BuiltinSftpClientExtensionsTest.java | 84 +++++++++
21 files changed, 1116 insertions(+), 30 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
index 7f86bd7..05d0a2e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/SubsystemClient.java
@@ -21,11 +21,15 @@ package org.apache.sshd.client.subsystem;
import java.nio.channels.Channel;
+import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.NamedResource;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public interface SubsystemClient extends NamedResource, Channel {
- // marker interface for subsystems
+ /**
+ * @return The underlying {@link ClientSession} used
+ */
+ ClientSession getClientSession();
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
index fc7a02b..1807cb4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java
@@ -25,15 +25,23 @@ import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient {
+public abstract class AbstractSftpClient extends AbstractLoggingBean implements SftpClient, RawSftpClient {
+ private final AtomicReference<Map<String,Object>> parsedExtensionsHolder = new AtomicReference<Map<String,Object>>(null);
+
protected AbstractSftpClient() {
super();
}
@@ -127,4 +135,36 @@ public abstract class AbstractSftpClient extends AbstractLoggingBean implements
public void symLink(String linkPath, String targetPath) throws IOException {
link(linkPath, targetPath, true);
}
+
+ @Override
+ public <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType) {
+ Object instance = getExtension(BuiltinSftpClientExtensions.fromType(extensionType));
+ if (instance == null) {
+ return null;
+ } else {
+ return extensionType.cast(instance);
+ }
+ }
+
+ @Override
+ public SftpClientExtension getExtension(String extensionName) {
+ return getExtension(BuiltinSftpClientExtensions.fromName(extensionName));
+ }
+
+ protected SftpClientExtension getExtension(SftpClientExtensionFactory factory) {
+ if (factory == null) {
+ return null;
+ }
+
+ Map<String,byte[]> extensions = getServerExtensions();
+ Map<String,Object> parsed = parsedExtensionsHolder.get();
+ if (parsed == null) {
+ if ((parsed=ParserUtils.parse(extensions)) == null) {
+ parsed = Collections.<String,Object>emptyMap();
+ }
+ parsedExtensionsHolder.set(parsed);
+ }
+
+ return factory.create(this, this, extensions, parsed);
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
index 440ba88..9523514 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultSftpClient.java
@@ -167,6 +167,11 @@ public class DefaultSftpClient extends AbstractSftpClient {
}
@Override
+ public ClientSession getClientSession() {
+ return clientSession;
+ }
+
+ @Override
public Map<String, byte[]> getServerExtensions() {
return exposedExtensions;
}
@@ -255,18 +260,20 @@ public class DefaultSftpClient extends AbstractSftpClient {
}
}
- protected int send(int cmd, Buffer buffer) throws IOException {
+ @Override
+ public int send(int cmd, Buffer buffer) throws IOException {
int id = cmdId.incrementAndGet();
OutputStream dos = channel.getInvertedIn();
BufferUtils.writeInt(dos, 1 /* cmd */ + (Integer.SIZE / Byte.SIZE) /* id */ + buffer.available(), workBuf);
- dos.write(cmd);
+ dos.write(cmd & 0xFF);
BufferUtils.writeInt(dos, id, workBuf);
dos.write(buffer.array(), buffer.rpos(), buffer.available());
dos.flush();
return id;
}
- protected Buffer receive(int id) throws IOException {
+ @Override
+ public Buffer receive(int id) throws IOException {
synchronized (messages) {
while (true) {
if (closing) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/RawSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/RawSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/RawSftpClient.java
new file mode 100644
index 0000000..ab80812
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/RawSftpClient.java
@@ -0,0 +1,44 @@
+/*
+ * 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.subsystem.sftp;
+
+import java.io.IOException;
+
+import org.apache.sshd.common.util.buffer.Buffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface RawSftpClient {
+ /**
+ * @param cmd Command to send - <B>Note:</B> only lower 8-bits are used
+ * @param buffer The {@link Buffer} containing the command data
+ * @return The assigned request id
+ * @throws IOException if failed to send command
+ */
+ int send(int cmd, Buffer buffer) throws IOException;
+
+ /**
+ * @param id The expected request id
+ * @return The received response {@link Buffer} containing the request id
+ * @throws IOException If connection closed or interrupted
+ */
+ Buffer receive(int id) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
index d7ded06..481ef3a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClient.java
@@ -36,6 +36,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.sshd.client.subsystem.SubsystemClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
@@ -313,4 +314,22 @@ public interface SftpClient extends SubsystemClient {
OutputStream write(String path, Collection<OpenMode> mode) throws IOException;
OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException;
+ /**
+ * @param extensionType The extension type
+ * @return The extension instance - <B>Note:</B> it is up to the caller
+ * to invoke {@link SftpClientExtension#isSupported()} - {@code null} if
+ * this extension type is not implemented by the client
+ * @see #getServerExtensions()
+ */
+ <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType);
+
+ /**
+ * @param extensionName The extension name
+ * @return The {@link SftpClientExtension} name - ignored if {@code null}/empty
+ * @return The extension instance - <B>Note:</B> it is up to the caller
+ * to invoke {@link SftpClientExtension#isSupported()} - {@code null} if
+ * this extension type is not implemented by the client
+ * @see #getServerExtensions()
+ */
+ SftpClientExtension getExtension(String extensionName);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
index f2abfa7..050716f 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
@@ -21,6 +21,7 @@ package org.apache.sshd.client.subsystem.sftp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.StreamCorruptedException;
import java.nio.file.FileStore;
import java.nio.file.FileSystemException;
import java.nio.file.attribute.GroupPrincipal;
@@ -41,6 +42,7 @@ import org.apache.sshd.common.FactoryManagerUtils;
import org.apache.sshd.common.file.util.BaseFileSystem;
import org.apache.sshd.common.file.util.ImmutableList;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
public class SftpFileSystem extends BaseFileSystem<SftpPath> {
public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
@@ -192,6 +194,11 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
}
@Override
+ public ClientSession getClientSession() {
+ return delegate.getClientSession();
+ }
+
+ @Override
public Map<String, byte[]> getServerExtensions() {
return delegate.getServerExtensions();
}
@@ -446,6 +453,32 @@ public class SftpFileSystem extends BaseFileSystem<SftpPath> {
}
delegate.unlock(handle, offset, length);
}
+
+ @Override
+ public int send(int cmd, Buffer buffer) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("send(cmd=" + cmd + ") client is closed");
+ }
+
+ if (delegate instanceof RawSftpClient) {
+ return ((RawSftpClient) delegate).send(cmd, buffer);
+ } else {
+ throw new StreamCorruptedException("send(cmd=" + cmd + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+ }
+ }
+
+ @Override
+ public Buffer receive(int id) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("receive(id=" + id + ") client is closed");
+ }
+
+ if (delegate instanceof RawSftpClient) {
+ return ((RawSftpClient) delegate).receive(id);
+ } else {
+ throw new StreamCorruptedException("receive(id=" + id + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+ }
+ }
}
protected static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
new file mode 100644
index 0000000..059966e
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensions.java
@@ -0,0 +1,112 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.impl.CopyFileExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.impl.MD5FileExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.impl.MD5HandleExtensionImpl;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinSftpClientExtensions implements SftpClientExtensionFactory {
+ COPY_FILE(SftpConstants.EXT_COPYFILE, CopyFileExtension.class) {
+ @Override // co-variant return
+ public CopyFileExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+ return new CopyFileExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+ }
+ },
+ MD5_FILE(SftpConstants.EXT_MD5HASH, MD5FileExtension.class) {
+ @Override // co-variant return
+ public MD5FileExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+ return new MD5FileExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+ }
+ },
+ MD5_HANDLE(SftpConstants.EXT_MD5HASH_HANDLE, MD5HandleExtension.class) {
+ @Override // co-variant return
+ public MD5HandleExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+ return new MD5HandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+ }
+ };
+
+ private final String name;
+
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ private final Class<? extends SftpClientExtension> type;
+ public final Class<? extends SftpClientExtension> getType() {
+ return type;
+ }
+
+ private BuiltinSftpClientExtensions(String name, Class<? extends SftpClientExtension> type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ @Override
+ public SftpClientExtension create(SftpClient client, RawSftpClient raw) {
+ Map<String,byte[]> extensions = client.getServerExtensions();
+ return create(client, raw, extensions, ParserUtils.parse(extensions));
+ }
+
+ public static final Set<BuiltinSftpClientExtensions> VALUES =
+ Collections.unmodifiableSet(EnumSet.allOf(BuiltinSftpClientExtensions.class));
+
+ public static final BuiltinSftpClientExtensions fromName(String n) {
+ return NamedResource.Utils.findByName(n, String.CASE_INSENSITIVE_ORDER, VALUES);
+ }
+
+ public static final BuiltinSftpClientExtensions fromInstance(Object o) {
+ return fromType((o == null) ? null : o.getClass());
+ }
+
+ public static final BuiltinSftpClientExtensions fromType(Class<?> type) {
+ if ((type == null) || (!SftpClientExtension.class.isAssignableFrom(type))) {
+ return null;
+ }
+
+ // the base class is assignable to everybody so we cannot distinguish between the enum(s)
+ if (SftpClientExtension.class == type) {
+ return null;
+ }
+
+ for (BuiltinSftpClientExtensions v : VALUES) {
+ Class<?> vt = v.getType();
+ if (vt.isAssignableFrom(type)) {
+ return v;
+ }
+ }
+
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
new file mode 100644
index 0000000..b78228f
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CopyFileExtension.java
@@ -0,0 +1,36 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00#section-6">copy-file</A> extension
+ */
+public interface CopyFileExtension extends SftpClientExtension {
+ /**
+ * @param src The (<U>remote</U>) file source path
+ * @param dst The (<U>remote</U>) file destination path
+ * @param overwriteDestination If {@code true} then OK to override destination if exists
+ * @throws IOException If failed to execute the command or extension not supported
+ */
+ void copyFile(String src, String dst, boolean overwriteDestination) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
new file mode 100644
index 0000000..c22b003
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5FileExtension.java
@@ -0,0 +1,39 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MD5FileExtension extends SftpClientExtension {
+ /**
+ * @param path The (remote) path
+ * @param offset The offset to start calculating the hash
+ * @param length The number of data bytes to calculate the hash on - if
+ * greater than available, then up to whatever is available
+ * @param quickHash A quick-hash of the 1st 2048 bytes - ignored if {@code null}/empty
+ * @return The hash value if the quick hash matches (or {@code null}/empty), or
+ * {@code null}/empty if the quick hash is provided and it does not match
+ * @throws IOException If failed to calculate the hash
+ */
+ byte[] getHash(String path, long offset, long length, byte[] quickHash) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
new file mode 100644
index 0000000..8e5fcf9
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/MD5HandleExtension.java
@@ -0,0 +1,42 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import java.io.IOException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MD5HandleExtension extends SftpClientExtension {
+ /**
+ * @param handle The (remote) file {@link SftpClient.Handle}
+ * @param offset The offset to start calculating the hash
+ * @param length The number of data bytes to calculate the hash on - if
+ * greater than available, then up to whatever is available
+ * @param quickHash A quick-hash of the 1st 2048 bytes - ignored if {@code null}/empty
+ * @return The hash value if the quick hash matches (or {@code null}/empty), or
+ * {@code null}/empty if the quick hash is provided and it does not match
+ * @throws IOException If failed to calculate the hash
+ */
+ byte[] getHash(SftpClient.Handle handle, long offset, long length, byte[] quickHash) throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
new file mode 100644
index 0000000..b9eb5d7
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtension.java
@@ -0,0 +1,34 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.OptionalFeature;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientExtension extends NamedResource, OptionalFeature {
+ /**
+ * @return The {@link SftpClient} used to issue the extended command
+ */
+ SftpClient getClient();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
new file mode 100644
index 0000000..3d07aa0
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/SftpClientExtensionFactory.java
@@ -0,0 +1,35 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import java.util.Map;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.NamedResource;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpClientExtensionFactory extends NamedResource {
+ // TODO make this a default method for JDK-8
+ SftpClientExtension create(SftpClient client, RawSftpClient raw);
+ SftpClientExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
new file mode 100644
index 0000000..12c51ca
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractMD5HashExtension.java
@@ -0,0 +1,74 @@
+/*
+ * 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.subsystem.sftp.extensions.impl;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractMD5HashExtension extends AbstractSftpClientExtension {
+ protected AbstractMD5HashExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
+ super(name, client, raw, extras);
+ }
+
+ protected byte[] doGetHash(String target, long offset, long length, byte[] quickHash) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ String opcode = getName();
+ buffer.putString(opcode);
+ buffer.putString(target);
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ buffer.putBytes((quickHash == null) ? GenericUtils.EMPTY_BYTE_ARRAY : quickHash);
+
+ if (log.isDebugEnabled()) {
+ log.debug("doGetHash({})[{}] - offset={}, length={}, quick-hash={}",
+ opcode, target, Long.valueOf(offset), Long.valueOf(length), BufferUtils.printHex(':', quickHash));
+ }
+
+ buffer = checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer)));
+ if (buffer == null) {
+ throw new StreamCorruptedException("Missing extended reply data");
+ }
+
+ String targetType = buffer.getString();
+ if (String.CASE_INSENSITIVE_ORDER.compare(targetType, opcode) != 0) {
+ throw new StreamCorruptedException("Mismatched reply target type: expected=" + opcode + ", actual=" + targetType);
+ }
+
+ byte[] hashValue = buffer.getBytes();
+ if (log.isDebugEnabled()) {
+ log.debug("doGetHash({})[{}] - offset={}, length={}, quick-hash={} - result={}",
+ opcode, target, Long.valueOf(offset), Long.valueOf(length),
+ BufferUtils.printHex(':', quickHash), BufferUtils.printHex(':', hashValue));
+ }
+
+ return hashValue;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
new file mode 100644
index 0000000..14eef9c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractSftpClientExtension.java
@@ -0,0 +1,132 @@
+/*
+ * 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.subsystem.sftp.extensions.impl;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.Collection;
+
+import org.apache.sshd.client.SftpException;
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClientExtension extends AbstractLoggingBean implements SftpClientExtension, RawSftpClient {
+ private final String name;
+ private final SftpClient client;
+ private final RawSftpClient raw;
+ private final boolean supported;
+
+ protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
+ this(name, client, raw, GenericUtils.isEmpty(extras) ? false : extras.contains(name));
+ }
+
+ protected AbstractSftpClientExtension(String name, SftpClient client, RawSftpClient raw, boolean supported) {
+ this.name = ValidateUtils.checkNotNullAndNotEmpty(name, "No extension name", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.client = ValidateUtils.checkNotNull(client, "No client instance", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.raw = ValidateUtils.checkNotNull(raw, "No raw access", GenericUtils.EMPTY_OBJECT_ARRAY);
+ this.supported = supported;
+ }
+
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ @Override
+ public final SftpClient getClient() {
+ return client;
+ }
+
+ protected void sendAndCheckExtendedCommandStatus(Buffer buffer) throws IOException {
+ int reqId = sendExtendedCommand(buffer);
+ if (log.isDebugEnabled()) {
+ log.debug("sendAndCheckExtendedCommandStatus(" + getName() + ") id=" + reqId);
+ }
+ checkStatus(receive(reqId));
+ }
+
+ protected int sendExtendedCommand(Buffer buffer) throws IOException {
+ return send(SftpConstants.SSH_FXP_EXTENDED, buffer);
+ }
+
+ @Override
+ public int send(int cmd, Buffer buffer) throws IOException {
+ return raw.send(cmd, buffer);
+ }
+
+ @Override
+ public Buffer receive(int id) throws IOException {
+ return raw.receive(id);
+ }
+
+ @Override
+ public final boolean isSupported() {
+ return supported;
+ }
+
+ protected void checkStatus(Buffer buffer) throws IOException {
+ if (checkExtendedReplyBuffer(buffer) != null) {
+ throw new StreamCorruptedException("Unexpected extended reply received");
+ }
+ }
+
+ /**
+ * @param buffer The {@link Buffer} to check
+ * @return The {@link Buffer} if this is an {@link SftpConstants#SSH_FXP_EXTENDED_REPLY},
+ * or {@code null} if this is a {@link SftpConstants#SSH_FXP_STATUS} carrying
+ * an {@link SftpConstants#SSH_FX_OK} result
+ * @throws IOException If a non-{@link SftpConstants#SSH_FX_OK} result or
+ * not a {@link SftpConstants#SSH_FXP_EXTENDED_REPLY} buffer
+ */
+ protected Buffer checkExtendedReplyBuffer(Buffer buffer) throws IOException {
+ int length = buffer.getInt();
+ int type = buffer.getUByte();
+ int id = buffer.getInt();
+ if (type == SftpConstants.SSH_FXP_STATUS) {
+ int substatus = buffer.getInt();
+ String msg = buffer.getString();
+ String lang = buffer.getString();
+ if (log.isDebugEnabled()) {
+ log.debug("checkStatus({}}[id={}] - status: {} [{}] {}",
+ getName(), Integer.valueOf(id), Integer.valueOf(substatus), lang, msg);
+ }
+
+ if (substatus != SftpConstants.SSH_FX_OK) {
+ throw new SftpException(substatus, msg);
+ }
+
+ return null;
+ } else if (type == SftpConstants.SSH_FXP_EXTENDED_REPLY) {
+ return buffer;
+ } else {
+ throw new SshException("Unexpected SFTP packet received: type=" + type + ", id=" + id + ", length=" + length);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
new file mode 100644
index 0000000..7186df6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CopyFileExtensionImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.subsystem.sftp.extensions.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.CopyFileExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CopyFileExtensionImpl extends AbstractSftpClientExtension implements CopyFileExtension {
+ public CopyFileExtensionImpl(SftpClient client, RawSftpClient raw, Collection<String> extra) {
+ super(SftpConstants.EXT_COPYFILE, client, raw, extra);
+ }
+
+ @Override
+ public void copyFile(String src, String dst, boolean overwriteDestination) throws IOException {
+ Buffer buffer = new ByteArrayBuffer((Integer.SIZE / Byte.SIZE) + GenericUtils.length(getName())
+ + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(src)
+ + (Integer.SIZE / Byte.SIZE) + GenericUtils.length(dst)
+ + 1 /* override destination */);
+ buffer.putString(getName());
+ buffer.putString(src);
+ buffer.putString(dst);
+ buffer.putBoolean(overwriteDestination);
+ sendAndCheckExtendedCommandStatus(buffer);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5FileExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5FileExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5FileExtensionImpl.java
new file mode 100644
index 0000000..b0340be
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5FileExtensionImpl.java
@@ -0,0 +1,42 @@
+/*
+ * 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.subsystem.sftp.extensions.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.MD5FileExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MD5FileExtensionImpl extends AbstractMD5HashExtension implements MD5FileExtension {
+ public MD5FileExtensionImpl(SftpClient client, RawSftpClient raw, Collection<String> extra) {
+ super(SftpConstants.EXT_MD5HASH, client, raw, extra);
+ }
+
+ @Override
+ public byte[] getHash(String path, long offset, long length, byte[] quickHash) throws IOException {
+ return doGetHash(path, offset, length, quickHash);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
new file mode 100644
index 0000000..e6ab476
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/MD5HandleExtensionImpl.java
@@ -0,0 +1,43 @@
+/*
+ * 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.subsystem.sftp.extensions.impl;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.extensions.MD5HandleExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MD5HandleExtensionImpl extends AbstractMD5HashExtension implements MD5HandleExtension {
+ public MD5HandleExtensionImpl(SftpClient client, RawSftpClient raw, Collection<String> extra) {
+ super(SftpConstants.EXT_MD5HASH_HANDLE, client, raw, extra);
+ }
+
+ @Override
+ public byte[] getHash(SftpClient.Handle handle, long offset, long length, byte[] quickHash) throws IOException {
+ return doGetHash(handle.id, offset, length, quickHash);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
index d64bebc..f883c89 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/extensions/ParserUtils.java
@@ -27,7 +27,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.TreeSet;
+import org.apache.sshd.common.subsystem.sftp.extensions.Supported2Parser.Supported2;
+import org.apache.sshd.common.subsystem.sftp.extensions.SupportedParser.Supported;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
@@ -118,6 +121,27 @@ public final class ParserUtils {
}
}
+ public static final Set<String> supportedExtensions(Map<String,?> parsed) {
+ if (GenericUtils.isEmpty(parsed)) {
+ return Collections.emptySet();
+ }
+
+ Supported sup = (Supported) parsed.get(SupportedParser.INSTANCE.getName());
+ Collection<String> extra = (sup == null) ? null : sup.extensionNames;
+ Supported2 sup2 = (Supported2) parsed.get(Supported2Parser.INSTANCE.getName());
+ Collection<String> extra2 = (sup2 == null) ? null : sup2.extensionNames;
+ if (GenericUtils.isEmpty(extra)) {
+ return GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, extra2);
+ } else if (GenericUtils.isEmpty(extra2)) {
+ return GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, extra);
+ }
+
+ Set<String> result = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+ result.addAll(extra);
+ result.addAll(extra2);
+ return result;
+ }
+
/**
* @param extensions The received extensions in encoded form
* @return A {@link Map} of all the successfully decoded extensions
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index 740ceb9..3371904 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -690,9 +690,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
Digest digest = BuiltinDigests.md5.create();
+ digest.init();
+
byte[] workBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
ByteBuffer bb = ByteBuffer.wrap(workBuf);
boolean hashMatches = false;
+ byte[] hashValue = null;
try(FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
channel.position(startOffset);
@@ -706,33 +709,54 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
* this case.
*/
if (GenericUtils.length(quickCheckHash) <= 0) {
- // TODO consider allowing it - e.g., if the requested effective length is <= than some (configurable) threshold
- throw new UnsupportedOperationException(targetType + " w/o q quick check hash is not supported");
+ // TODO consider limiting it - e.g., if the requested effective length is <= than some (configurable) threshold
+ hashMatches = true;
+ } else {
+ int readLen = channel.read(bb);
+ effectiveLength -= readLen;
+ digest.update(workBuf, 0, readLen);
+
+ hashValue = digest.digest();
+ hashMatches = Arrays.equals(quickCheckHash, hashValue);
+ if (hashMatches) {
+ /*
+ * Need to re-initialize the digester due to the Javadoc:
+ *
+ * "The digest method can be called once for a given number
+ * of updates. After digest has been called, the MessageDigest
+ * object is reset to its initialized state."
+ */
+ if (effectiveLength > 0L) {
+ digest = BuiltinDigests.md5.create();
+ digest.init();
+ digest.update(workBuf, 0, readLen);
+ hashValue = null; // start again
+ }
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("doMD5Hash({})[{}] offset={}, length={} - quick-hash mismatched expected={}, actual={}",
+ targetType, target, Long.valueOf(startOffset), Long.valueOf(length),
+ BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
+ }
+ }
}
-
- int readLen = channel.read(bb);
- effectiveLength -= readLen;
- digest.update(workBuf, 0, readLen);
- byte[] hashValue = digest.digest();
- hashMatches = Arrays.equals(quickCheckHash, hashValue);
if (hashMatches) {
while(effectiveLength > 0L) {
bb.clear();
- readLen = channel.read(bb);
+ int readLen = channel.read(bb);
effectiveLength -= readLen;
digest.update(workBuf, 0, readLen);
}
- } else {
- if (log.isTraceEnabled()) {
- log.trace("doMD5Hash({})[{}] offset={}, length={} - quick-hash mismatched expected={}, actual={}",
- targetType, target, Long.valueOf(startOffset), Long.valueOf(length),
- BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
+
+ if (hashValue == null) { // check if did any more iterations after the quick hash
+ hashValue = digest.digest();
}
+ } else {
+ hashValue = GenericUtils.EMPTY_BYTE_ARRAY;
}
}
- byte[] hashValue = hashMatches ? digest.digest() : GenericUtils.EMPTY_BYTE_ARRAY;
if (log.isDebugEnabled()) {
log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={} - match={}, hash={}",
targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash),
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
index ad56705..b1e12db 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpTest.java
@@ -30,8 +30,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
@@ -43,9 +45,18 @@ import java.util.Set;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
+import org.apache.sshd.client.SftpException;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions;
+import org.apache.sshd.client.subsystem.sftp.extensions.CopyFileExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.MD5FileExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.MD5HandleExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.digest.Digest;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.file.root.RootedFileSystemProvider;
import org.apache.sshd.common.session.Session;
@@ -55,6 +66,7 @@ import org.apache.sshd.common.subsystem.sftp.extensions.Supported2Parser.Support
import org.apache.sshd.common.subsystem.sftp.extensions.SupportedParser.Supported;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.server.Command;
@@ -78,7 +90,6 @@ import org.junit.runners.MethodSorters;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.SftpException;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SftpTest extends BaseTestSupport {
@@ -520,7 +531,7 @@ public class SftpTest extends BaseTestSupport {
real = c.realpath(path + "/foobar");
System.out.println(real);
fail("Expected SftpException");
- } catch (SftpException e) {
+ } catch (com.jcraft.jsch.SftpException e) {
// ok
}
} finally {
@@ -530,6 +541,14 @@ public class SftpTest extends BaseTestSupport {
@Test
public void testRename() throws Exception {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
+
try(SshClient client = SshClient.setUpDefaultClient()) {
client.start();
@@ -537,14 +556,6 @@ public class SftpTest extends BaseTestSupport {
session.addPasswordIdentity(getCurrentTestName());
session.auth().verify(5L, TimeUnit.SECONDS);
- Path targetPath = detectTargetFolder().toPath();
- Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- Utils.deleteRecursive(lclSftp);
- Files.createDirectories(lclSftp);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client"));
-
try(SftpClient sftp = session.createSftpClient()) {
Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt");
String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1);
@@ -583,6 +594,152 @@ public class SftpTest extends BaseTestSupport {
}
@Test
+ public void testCopyFileExtension() throws Exception {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ Path srcFile = lclSftp.resolve("src.txt");
+ Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
+
+ Path parentPath = targetPath.getParent();
+ String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+ Path dstFile = lclSftp.resolve("dst.txt");
+ String dstPath = Utils.resolveRelativeRemotePath(parentPath, dstFile);
+
+ LinkOption[] options = IoUtils.getLinkOptions(false);
+ assertFalse("Destination file unexpectedly exists", Files.exists(dstFile, options));
+
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try(SftpClient sftp = session.createSftpClient()) {
+ CopyFileExtension ext = assertExtensionCreated(sftp, CopyFileExtension.class);
+ ext.copyFile(srcPath, dstPath, false);
+ assertTrue("Source file not preserved", Files.exists(srcFile, options));
+ assertTrue("Destination file not created", Files.exists(dstFile, options));
+
+ byte[] actual = Files.readAllBytes(dstFile);
+ assertArrayEquals("Mismatched copied data", data, actual);
+
+ try {
+ ext.copyFile(srcPath, dstPath, false);
+ fail("Unexpected success to overwrite existing destination: " + dstFile);
+ } catch(IOException e) {
+ assertTrue("Not an SftpException", e instanceof SftpException);
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testMD5HashExtensionOnSmallFile() throws Exception {
+ testMD5HashExtension((getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testMD5HashExtensionOnLargeFile() throws Exception {
+ byte[] seed = (getClass().getName() + "#" + getCurrentTestName() + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
+ final int TEST_SIZE = Byte.SIZE * SftpConstants.MD5_QUICK_HASH_SIZE;
+ try(ByteArrayOutputStream baos=new ByteArrayOutputStream(TEST_SIZE + seed.length)) {
+ while (baos.size() < TEST_SIZE) {
+ baos.write(seed);
+ }
+
+ testMD5HashExtension(baos.toByteArray());
+ }
+ }
+
+ private void testMD5HashExtension(byte[] data) throws Exception {
+ Path targetPath = detectTargetFolder().toPath();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+ Files.createDirectories(lclSftp);
+
+ Digest digest = BuiltinDigests.md5.create();
+ digest.init();
+ digest.update(data);
+
+ byte[] expectedHash = digest.digest();
+ byte[] quickHash = expectedHash;
+ if (data.length > SftpConstants.MD5_QUICK_HASH_SIZE) {
+ byte[] quickData = new byte[SftpConstants.MD5_QUICK_HASH_SIZE];
+ System.arraycopy(data, 0, quickData, 0, quickData.length);
+ digest = BuiltinDigests.md5.create();
+ digest.init();
+ digest.update(quickData);
+ quickHash = digest.digest();
+ }
+
+ Path srcFile = lclSftp.resolve("src.txt");
+ Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
+
+ Path parentPath = targetPath.getParent();
+ String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+ String srcFolder = Utils.resolveRelativeRemotePath(parentPath, srcFile.getParent());
+
+ try(SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try(SftpClient sftp = session.createSftpClient()) {
+ MD5FileExtension file = assertExtensionCreated(sftp, MD5FileExtension.class);
+ try {
+ byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash);
+ fail("Unexpected file success on folder=" + srcFolder + ": " + BufferUtils.printHex(':', actual));
+ } catch(IOException e) { // expected - not allowed to hash a folder
+ assertTrue("Not an SftpException", e instanceof SftpException);
+ }
+
+ MD5HandleExtension hndl = assertExtensionCreated(sftp, MD5HandleExtension.class);
+ try(CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
+ try {
+ byte[] actual = hndl.getHash(dirHandle, 0L, 0L, quickHash);
+ fail("Unexpected handle success on folder=" + srcFolder + ": " + BufferUtils.printHex(':', actual));
+ } catch(IOException e) { // expected - not allowed to hash a folder
+ assertTrue("Not an SftpException", e instanceof SftpException);
+ }
+ }
+
+ try(CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
+ for (byte[] qh : new byte[][] { GenericUtils.EMPTY_BYTE_ARRAY, quickHash }) {
+ for (boolean useFile : new boolean[] { true, false }) {
+ byte[] actualHash = useFile ? file.getHash(srcPath, 0L, 0L, qh) : hndl.getHash(fileHandle, 0L, 0L, qh);
+ String type = useFile ? file.getClass().getSimpleName() : hndl.getClass().getSimpleName();
+ if (!Arrays.equals(expectedHash, actualHash)) {
+ fail("Mismatched hash for quick=" + BufferUtils.printHex(':', qh) + " using " + type
+ + ": expected=" + BufferUtils.printHex(':', expectedHash)
+ + ", actual=" + BufferUtils.printHex(':', actualHash));
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+ private static <E extends SftpClientExtension> E assertExtensionCreated(SftpClient sftp, Class<E> type) {
+ E instance = sftp.getExtension(type);
+ assertNotNull("Extension not created: " + type.getSimpleName(), instance);
+ assertTrue("Extension not supported: " + instance.getName(), instance.isSupported());
+ return instance;
+ }
+
+ @Test
public void testServerExtensionsDeclarations() throws Exception {
try(SshClient client = SshClient.setUpDefaultClient()) {
client.start();
@@ -612,6 +769,14 @@ public class SftpTest extends BaseTestSupport {
assertSupportedExtensions(extName, ((Supported2) extValue).extensionNames);
}
}
+
+ for (BuiltinSftpClientExtensions type : BuiltinSftpClientExtensions.VALUES) {
+ String extensionName = type.getName();
+ SftpClientExtension instance = sftp.getExtension(extensionName);
+ assertNotNull("Extension not implemented:" + extensionName, instance);
+ assertEquals("Mismatched instance name", extensionName, instance.getName());
+ assertTrue("Extension not supported: " + extensionName, instance.isSupported());
+ }
}
} finally {
client.stop();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/f8a0e812/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java
new file mode 100644
index 0000000..11be24f
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.subsystem.sftp.extensions;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.util.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class BuiltinSftpClientExtensionsTest extends BaseTestSupport {
+ public BuiltinSftpClientExtensionsTest() {
+ super();
+ }
+
+ @Test
+ public void testFromName() {
+ for (String name : new String[] { null, "", getCurrentTestName() }) {
+ assertNull("Unexpected result for name='" + name + "'", BuiltinSftpClientExtensions.fromName(name));
+ }
+
+ for (BuiltinSftpClientExtensions expected : BuiltinSftpClientExtensions.VALUES) {
+ String name = expected.getName();
+ for (int index = 0; index < name.length(); index++) {
+ BuiltinSftpClientExtensions actual = BuiltinSftpClientExtensions.fromName(name);
+ assertSame(name, expected, actual);
+ name = shuffleCase(name);
+ }
+ }
+ }
+
+ @Test
+ public void testFromType() {
+ for (Class<?> clazz : new Class<?>[] { null, getClass(), SftpClientExtension.class }) {
+ assertNull("Unexpected value for class=" + clazz, BuiltinSftpClientExtensions.fromType(clazz));
+ }
+
+ for (BuiltinSftpClientExtensions expected : BuiltinSftpClientExtensions.VALUES) {
+ Class<?> type = expected.getType();
+ BuiltinSftpClientExtensions actual = BuiltinSftpClientExtensions.fromType(type);
+ assertSame(type.getSimpleName(), expected, actual);
+ }
+ }
+
+ @Test
+ public void testFromInstance() {
+ for (Object instance : new Object[] { null, this }) {
+ assertNull("Unexpected value for " + instance, BuiltinSftpClientExtensions.fromInstance(instance));
+ }
+
+ SftpClient mockClient = Mockito.mock(SftpClient.class);
+ RawSftpClient mockRaw = Mockito.mock(RawSftpClient.class);
+
+ for (BuiltinSftpClientExtensions expected : BuiltinSftpClientExtensions.VALUES) {
+ SftpClientExtension e = expected.create(mockClient, mockRaw);
+ BuiltinSftpClientExtensions actual = BuiltinSftpClientExtensions.fromInstance(e);
+ assertSame(expected.getName(), expected, actual);
+ assertEquals("Mismatched extension name", expected.getName(), actual.getName());
+ }
+ }
+}