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/07 13:44:06 UTC
mina-sshd git commit: [SSHD-519] Add support for SFTP
'check-file-handle'/'check-file-name' extensions
Repository: mina-sshd
Updated Branches:
refs/heads/master 1806ac970 -> 4818061d8
[SSHD-519] Add support for SFTP 'check-file-handle'/'check-file-name' extensions
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/4818061d
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/4818061d
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/4818061d
Branch: refs/heads/master
Commit: 4818061d8dec69b74da643e8ecd2633fa3be4b89
Parents: 1806ac9
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue Jul 7 14:43:55 2015 +0300
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue Jul 7 14:43:55 2015 +0300
----------------------------------------------------------------------
.../extensions/BuiltinSftpClientExtensions.java | 14 ++
.../extensions/CheckFileHandleExtension.java | 46 ++++
.../sftp/extensions/CheckFileNameExtension.java | 44 ++++
.../sftp/extensions/MD5FileExtension.java | 1 +
.../sftp/extensions/MD5HandleExtension.java | 1 +
.../impl/AbstractCheckFileExtension.java | 84 +++++++
.../impl/AbstractMD5HashExtension.java | 3 +-
.../impl/CheckFileHandleExtensionImpl.java | 44 ++++
.../impl/CheckFileNameExtensionImpl.java | 43 ++++
.../common/subsystem/sftp/SftpConstants.java | 4 +
.../server/subsystem/sftp/SftpSubsystem.java | 191 ++++++++++++++-
.../sftp/AbstractSftpClientTestSupport.java | 90 +++++++
.../sshd/client/subsystem/sftp/SftpTest.java | 128 +---------
.../AbstractCheckFileExtensionTest.java | 234 +++++++++++++++++++
.../AbstractMD5HashExtensionTest.java | 181 ++++++++++++++
15 files changed, 972 insertions(+), 136 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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
index 059966e..7a23f13 100644
--- 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
@@ -26,6 +26,8 @@ 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.CheckFileHandleExtensionImpl;
+import org.apache.sshd.client.subsystem.sftp.extensions.impl.CheckFileNameExtensionImpl;
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;
@@ -54,6 +56,18 @@ public enum BuiltinSftpClientExtensions implements SftpClientExtensionFactory {
public MD5HandleExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
return new MD5HandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
}
+ },
+ CHECK_FILE_NAME(SftpConstants.EXT_CHKFILE_NAME, CheckFileNameExtension.class) {
+ @Override // co-variant return
+ public CheckFileNameExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+ return new CheckFileNameExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+ }
+ },
+ CHECK_FILE_HANDLE(SftpConstants.EXT_CHKFILE_HANDLE, CheckFileHandleExtension.class) {
+ @Override // co-variant return
+ public CheckFileHandleExtension create(SftpClient client, RawSftpClient raw, Map<String,byte[]> extensions, Map<String,?> parsed) {
+ return new CheckFileHandleExtensionImpl(client, raw, ParserUtils.supportedExtensions(parsed));
+ }
};
private final String name;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java
new file mode 100644
index 0000000..40bdd4c
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileHandleExtension.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.util.Collection;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle;
+import org.apache.sshd.common.util.Pair;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.2</A>
+ */
+public interface CheckFileHandleExtension extends SftpClientExtension {
+ /**
+ * @param handle Remote file {@link Handle} - must be a file and opened for read
+ * @param algorithms Hash algorithms in preferred order
+ * @param startOffset Start offset of the hash
+ * @param length Length of data to hash - if zero then till EOF
+ * @param blockSize Input block size to calculate individual hashes - if
+ * zero the <U>one</U> hash of <U>all</U> the data
+ * @return A {@link Pair} where left=hash algorithm name, right=the calculated
+ * hashes.
+ * @throws IOException If failed to execute the command
+ */
+ Pair<String,Collection<byte[]>> checkFileHandle(Handle handle, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException;
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.java
new file mode 100644
index 0000000..cb908f4
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/CheckFileNameExtension.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.extensions;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.sshd.common.util.Pair;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.2</A>
+ */
+public interface CheckFileNameExtension extends SftpClientExtension {
+ /**
+ * @param name Remote file name/path
+ * @param algorithms Hash algorithms in preferred order
+ * @param startOffset Start offset of the hash
+ * @param length Length of data to hash - if zero then till EOF
+ * @param blockSize Input block size to calculate individual hashes - if
+ * zero the <U>one</U> hash of <U>all</U> the data
+ * @return A {@link Pair} where left=hash algorithm name, right=the calculated
+ * hashes.
+ * @throws IOException If failed to execute the command
+ */
+ Pair<String,Collection<byte[]>> checkFileName(String name, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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
index c22b003..4706e76 100644
--- 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
@@ -23,6 +23,7 @@ import java.io.IOException;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.1</A>
*/
public interface MD5FileExtension extends SftpClientExtension {
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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
index 8e5fcf9..1188b50 100644
--- 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
@@ -25,6 +25,7 @@ import org.apache.sshd.client.subsystem.sftp.SftpClient;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ * @see <A HREF="http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt">DRAFT 09 - section 9.1.1</A>
*/
public interface MD5HandleExtension extends SftpClientExtension {
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.java
new file mode 100644
index 0000000..a31f8e5
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/AbstractCheckFileExtension.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.impl;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Pair;
+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 AbstractCheckFileExtension extends AbstractSftpClientExtension {
+ protected AbstractCheckFileExtension(String name, SftpClient client, RawSftpClient raw, Collection<String> extras) {
+ super(name, client, raw, extras);
+ }
+
+ protected Pair<String,Collection<byte[]>> doGetHash(Object target, Collection<String> algorithms, long offset, long length, int blockSize) throws IOException {
+ Buffer buffer = new ByteArrayBuffer();
+ String opcode = getName();
+ buffer.putString(opcode);
+ if (target instanceof CharSequence) {
+ buffer.putString(target.toString());
+ } else {
+ buffer.putBytes((byte[]) target);
+ }
+ buffer.putString(GenericUtils.join(algorithms, ','));
+ buffer.putLong(offset);
+ buffer.putLong(length);
+ buffer.putInt(blockSize);
+
+ if (log.isDebugEnabled()) {
+ log.debug("doGetHash({})[{}] - offset={}, length={}, block-size={}",
+ opcode, (target instanceof CharSequence) ? target : BufferUtils.printHex(BufferUtils.EMPTY_HEX_SEPARATOR, (byte[]) target),
+ Long.valueOf(offset), Long.valueOf(length), Integer.valueOf(blockSize));
+ }
+
+ 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, SftpConstants.EXT_CHKFILE_RESPONSE) != 0) {
+ throw new StreamCorruptedException("Mismatched reply type: expected=" + SftpConstants.EXT_CHKFILE_RESPONSE + ", actual=" + targetType);
+ }
+
+ String algo = buffer.getString();
+ Collection<byte[]> hashes = new LinkedList<>();
+ while (buffer.available() > 0) {
+ byte[] hashValue = buffer.getBytes();
+ hashes.add(hashValue);
+ }
+
+ return new Pair<String, Collection<byte[]>>(algo, hashes);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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
index 7aa7778..b7c138b 100644
--- 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
@@ -53,7 +53,8 @@ public abstract class AbstractMD5HashExtension extends AbstractSftpClientExtensi
if (log.isDebugEnabled()) {
log.debug("doGetHash({})[{}] - offset={}, length={}, quick-hash={}",
- opcode, target, Long.valueOf(offset), Long.valueOf(length), BufferUtils.printHex(':', quickHash));
+ opcode, (target instanceof CharSequence) ? target : BufferUtils.printHex(BufferUtils.EMPTY_HEX_SEPARATOR, (byte[]) target),
+ Long.valueOf(offset), Long.valueOf(length), BufferUtils.printHex(':', quickHash));
}
buffer = checkExtendedReplyBuffer(receive(sendExtendedCommand(buffer)));
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileHandleExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileHandleExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileHandleExtensionImpl.java
new file mode 100644
index 0000000..fabc3f3
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileHandleExtensionImpl.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.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.SftpClient.Handle;
+import org.apache.sshd.client.subsystem.sftp.extensions.CheckFileHandleExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.Pair;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CheckFileHandleExtensionImpl extends AbstractCheckFileExtension implements CheckFileHandleExtension {
+ public CheckFileHandleExtensionImpl(SftpClient client, RawSftpClient raw, Collection<String> extras) {
+ super(SftpConstants.EXT_CHKFILE_HANDLE, client, raw, extras);
+ }
+
+ @Override
+ public Pair<String, Collection<byte[]>> checkFileHandle(Handle handle, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException {
+ return doGetHash(handle.getIdentifier(), algorithms, startOffset, length, blockSize);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileNameExtensionImpl.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileNameExtensionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileNameExtensionImpl.java
new file mode 100644
index 0000000..b747cb0
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/extensions/impl/CheckFileNameExtensionImpl.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.CheckFileNameExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.Pair;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class CheckFileNameExtensionImpl extends AbstractCheckFileExtension implements CheckFileNameExtension {
+ public CheckFileNameExtensionImpl(SftpClient client, RawSftpClient raw, Collection<String> extras) {
+ super(SftpConstants.EXT_CHKFILE_NAME, client, raw, extras);
+ }
+
+ @Override
+ public Pair<String, Collection<byte[]>> checkFileName(String name, Collection<String> algorithms, long startOffset, long length, int blockSize) throws IOException {
+ return doGetHash(name, algorithms, startOffset, length, blockSize);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
index 4aa555b..92be24e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/subsystem/sftp/SftpConstants.java
@@ -232,6 +232,10 @@ public final class SftpConstants {
public static final String EXT_MD5HASH = "md5-hash";
public static final String EXT_MD5HASH_HANDLE = "md5-hash-handle";
public static final int MD5_QUICK_HASH_SIZE = 2048;
+ public static final String EXT_CHKFILE_HANDLE = "check-file-handle";
+ public static final String EXT_CHKFILE_NAME = "check-file-name";
+ public static final int MIN_CHKFILE_BLOCKSIZE = 256;
+ public static final String EXT_CHKFILE_RESPONSE = "check-file";
private SftpConstants() {
throw new UnsupportedOperationException("No instance");
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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 d2a8ce4..41b9cf3 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
@@ -37,6 +37,7 @@ import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
+import java.nio.file.FileSystemLoopException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
@@ -80,6 +81,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.FactoryManagerUtils;
+import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.config.VersionProperties;
import org.apache.sshd.common.digest.BuiltinDigests;
import org.apache.sshd.common.digest.Digest;
@@ -176,7 +178,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
SftpConstants.EXT_VERSELECT,
SftpConstants.EXT_COPYFILE,
SftpConstants.EXT_MD5HASH,
- SftpConstants.EXT_MD5HASH_HANDLE
+ SftpConstants.EXT_MD5HASH_HANDLE,
+ SftpConstants.EXT_CHKFILE_HANDLE,
+ SftpConstants.EXT_CHKFILE_NAME
)));
static {
@@ -691,6 +695,10 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
case SftpConstants.EXT_MD5HASH_HANDLE:
doMD5Hash(buffer, id, extension);
break;
+ case SftpConstants.EXT_CHKFILE_HANDLE:
+ case SftpConstants.EXT_CHKFILE_NAME:
+ doCheckFileHash(buffer, id, extension);
+ break;
default:
log.info("Received unsupported SSH_FXP_EXTENDED({})", extension);
sendStatus(BufferUtils.clear(buffer), id, SSH_FX_OP_UNSUPPORTED, "Command SSH_FXP_EXTENDED(" + extension + ") is unsupported or not implemented");
@@ -722,6 +730,149 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
throw new UnsupportedOperationException("doTextSeek(" + fileHandle + ")");
}
+ protected void doCheckFileHash(Buffer buffer, int id, String targetType) throws IOException {
+ String target = buffer.getString();
+ String algList = buffer.getString();
+ String[] algos = GenericUtils.split(algList, ',');
+ long startOffset = buffer.getLong();
+ long length = buffer.getLong();
+ int blockSize = buffer.getInt();
+ try {
+ buffer.clear();
+ buffer.putByte((byte) SSH_FXP_EXTENDED_REPLY);
+ buffer.putInt(id);
+ buffer.putString(SftpConstants.EXT_CHKFILE_RESPONSE);
+ doCheckFileHash(id, targetType, target, Arrays.asList(algos), startOffset, length, blockSize, buffer);
+ } catch(Exception e) {
+ sendStatus(BufferUtils.clear(buffer), id, e);
+ return;
+ }
+
+ send(buffer);
+ }
+
+ protected void doCheckFileHash(int id, String targetType, String target, Collection<String> algos,
+ long startOffset, long length, int blockSize, Buffer buffer)
+ throws Exception {
+ Path path;
+ if (SftpConstants.EXT_CHKFILE_HANDLE.equalsIgnoreCase(targetType)) {
+ Handle h = handles.get(target);
+ FileHandle fileHandle = validateHandle(target, h, FileHandle.class);
+ path = fileHandle.getFile();
+
+ /*
+ * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.2:
+ *
+ * If ACE4_READ_DATA was not included when the file was opened,
+ * the server MUST return STATUS_PERMISSION_DENIED.
+ */
+ int access = fileHandle.getAccessMask();
+ if ((access & ACE4_READ_DATA) == 0) {
+ throw new AccessDeniedException("File not opened for read: " + path);
+ }
+ } else {
+ path = resolveFile(target);
+
+ /*
+ * To quote http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt section 9.1.2:
+ *
+ * If 'check-file-name' refers to a SSH_FILEXFER_TYPE_SYMLINK, the
+ * target should be opened.
+ */
+ for (int index=0; Files.isSymbolicLink(path) && (index < Byte.MAX_VALUE /* TODO make this configurable */); index++) {
+ path = Files.readSymbolicLink(path);
+ }
+
+ if (Files.isSymbolicLink(path)) {
+ throw new FileSystemLoopException(target + " yields a circular or too long chain of symlinks");
+ }
+
+ if (Files.isDirectory(path, IoUtils.getLinkOptions(false))) {
+ throw new NotDirectoryException(path.toString());
+ }
+ }
+
+ ValidateUtils.checkNotNullAndNotEmpty(algos, "No hash algorithms specified", GenericUtils.EMPTY_OBJECT_ARRAY);
+
+ NamedFactory<? extends Digest> factory = null;
+ for (String a : algos) {
+ if ((factory = BuiltinDigests.fromFactoryName(a)) != null) {
+ break;
+ }
+ }
+ ValidateUtils.checkNotNull(factory, "No matching digest factory found for %s", algos);
+
+ doCheckFileHash(id, path, factory, startOffset, length, blockSize, buffer);
+ }
+
+ protected void doCheckFileHash(int id, Path file, NamedFactory<? extends Digest> factory,
+ long startOffset, long length, int blockSize, Buffer buffer)
+ throws Exception {
+ ValidateUtils.checkTrue((blockSize == 0) || (blockSize >= SftpConstants.MIN_CHKFILE_BLOCKSIZE), "Invalid block size: %d", blockSize);
+ ValidateUtils.checkNotNull(factory, "No digest factory provided", GenericUtils.EMPTY_OBJECT_ARRAY);
+ buffer.putString(factory.getName());
+
+ long effectiveLength = length;
+ if (effectiveLength == 0L) {
+ long totalLength = Files.size(file);
+ effectiveLength = totalLength - startOffset;
+ }
+
+ byte[] workBuf = (blockSize == 0)
+ ? new byte[Math.min((int) effectiveLength, IoUtils.DEFAULT_COPY_SIZE)]
+ : new byte[Math.min((int) effectiveLength, blockSize)]
+ ;
+ ByteBuffer bb = ByteBuffer.wrap(workBuf);
+ try(FileChannel channel = FileChannel.open(file, IoUtils.EMPTY_OPEN_OPTIONS)) {
+ channel.position(startOffset);
+
+ Digest digest = factory.create();
+ digest.init();
+
+ if (blockSize == 0) {
+ while(effectiveLength > 0L) {
+ bb.clear(); // prepare for next read
+
+ int readLen = channel.read(bb);
+ if (readLen < 0) {
+ break;
+ }
+
+ effectiveLength -= readLen;
+ digest.update(workBuf, 0, readLen);
+ }
+
+ byte[] hashValue = digest.digest();
+ if (log.isTraceEnabled()) {
+ log.trace("doCheckFileHash({}) offset={}, length={} - hash={}",
+ file, Long.valueOf(startOffset), Long.valueOf(length),
+ BufferUtils.printHex(':', hashValue));
+ }
+ buffer.putBytes(hashValue);
+ } else {
+ for (int count=0; effectiveLength > 0L; count++) {
+ bb.clear(); // prepare for next read
+
+ int readLen = channel.read(bb);
+ if (readLen < 0) {
+ break;
+ }
+
+ effectiveLength -= readLen;
+ digest.update(workBuf, 0, readLen);
+
+ byte[] hashValue = digest.digest(); // NOTE: this also resets the hash for the next read
+ if (log.isTraceEnabled()) {
+ log.trace("doCheckFileHash({})[{}] offset={}, length={} - hash={}",
+ file, Integer.valueOf(count), Long.valueOf(startOffset), Long.valueOf(length),
+ BufferUtils.printHex(':', hashValue));
+ }
+ buffer.putBytes(hashValue);
+ }
+ }
+ }
+ }
+
protected void doMD5Hash(Buffer buffer, int id, String targetType) throws IOException {
String target = buffer.getString();
long startOffset = buffer.getLong();
@@ -730,6 +881,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
try {
hashValue = doMD5Hash(id, targetType, target, startOffset, length, quickCheckHash);
+ if (log.isTraceEnabled()) {
+ log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={} - hash={}",
+ targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash),
+ BufferUtils.printHex(':', hashValue));
+ }
+
} catch(Exception e) {
sendStatus(BufferUtils.clear(buffer), id, e);
return;
@@ -782,10 +939,15 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
if ((startOffset == 0L) && (length == 0L)) {
effectiveLength = Files.size(path);
}
-
+
+ return doMD5Hash(id, path, startOffset, effectiveLength, quickCheckHash);
+ }
+
+ protected byte[] doMD5Hash(int id, Path path, long startOffset, long length, byte[] quickCheckHash) throws Exception {
Digest digest = BuiltinDigests.md5.create();
digest.init();
+ long effectiveLength = length;
byte[] digestBuf = new byte[(int) Math.min(effectiveLength, SftpConstants.MD5_QUICK_HASH_SIZE)];
ByteBuffer bb = ByteBuffer.wrap(digestBuf);
boolean hashMatches = false;
@@ -807,6 +969,9 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
hashMatches = true;
} else {
int readLen = channel.read(bb);
+ if (readLen < 0) {
+ throw new EOFException("EOF while read initial buffer from " + path);
+ }
effectiveLength -= readLen;
digest.update(digestBuf, 0, readLen);
@@ -828,8 +993,8 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
} else {
if (log.isTraceEnabled()) {
- log.trace("doMD5Hash({})[{}] offset={}, length={} - quick-hash mismatched expected={}, actual={}",
- targetType, target, Long.valueOf(startOffset), Long.valueOf(length),
+ log.trace("doMD5Hash({}) offset={}, length={} - quick-hash mismatched expected={}, actual={}",
+ path, Long.valueOf(startOffset), Long.valueOf(length),
BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
}
}
@@ -837,8 +1002,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
if (hashMatches) {
while(effectiveLength > 0L) {
- bb.clear();
- int readLen = channel.read(bb);
+ bb.clear(); // prepare for next read
+
+ int readLen = channel.read(bb);
+ if (readLen < 0) {
+ break; // user may have specified more than we have available
+ }
effectiveLength -= readLen;
digest.update(digestBuf, 0, readLen);
}
@@ -851,12 +1020,12 @@ public class SftpSubsystem extends AbstractLoggingBean implements Command, Runna
}
}
- if (log.isDebugEnabled()) {
- log.debug("doMD5Hash({})[{}] offset={}, length={}, quick-hash={} - match={}, hash={}",
- targetType, target, Long.valueOf(startOffset), Long.valueOf(length), BufferUtils.printHex(':', quickCheckHash),
- Boolean.valueOf(hashMatches), BufferUtils.printHex(':', hashValue));
+ if (log.isTraceEnabled()) {
+ log.trace("doMD5Hash({}) offset={}, length={} - matches={}, quick={} hash={}",
+ path, Long.valueOf(startOffset), Long.valueOf(length), Boolean.valueOf(hashMatches),
+ BufferUtils.printHex(':', quickCheckHash), BufferUtils.printHex(':', hashValue));
}
-
+
return hashValue;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
new file mode 100644
index 0000000..d4b7294
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClientTestSupport.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.root.RootedFileSystemProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.BaseTestSupport;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.Utils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public abstract class AbstractSftpClientTestSupport extends BaseTestSupport {
+ protected SshServer sshd;
+ protected int port;
+ protected final FileSystemFactory fileSystemFactory;
+
+ protected AbstractSftpClientTestSupport() throws IOException {
+ Path targetPath = detectTargetFolder().toPath();
+ Path parentPath = targetPath.getParent();
+ final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
+ fileSystemFactory = new FileSystemFactory() {
+ @Override
+ public FileSystem createFileSystem(Session session) throws IOException {
+ return fileSystem;
+ }
+ };
+ }
+
+ protected void setupServer() throws Exception {
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+ sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
+ sshd.setCommandFactory(new ScpCommandFactory());
+ sshd.setShellFactory(new EchoShellFactory());
+ sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
+ sshd.setFileSystemFactory(fileSystemFactory);
+ sshd.start();
+ port = sshd.getPort();
+ }
+
+ protected void tearDownServer() throws Exception {
+ if (sshd != null) {
+ try {
+ sshd.stop(true);
+ } finally {
+ sshd = null;
+ }
+ }
+ }
+
+ protected static final <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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/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 f7ec553..d1e200a 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
@@ -97,37 +97,17 @@ import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class SftpTest extends BaseTestSupport {
+public class SftpTest extends AbstractSftpClientTestSupport {
- private SshServer sshd;
- private int port;
private com.jcraft.jsch.Session session;
- private final FileSystemFactory fileSystemFactory;
public SftpTest() throws IOException {
- Path targetPath = detectTargetFolder().toPath();
- Path parentPath = targetPath.getParent();
- final FileSystem fileSystem = new RootedFileSystemProvider().newFileSystem(parentPath, Collections.<String,Object>emptyMap());
- fileSystemFactory = new FileSystemFactory() {
- @Override
- public FileSystem createFileSystem(Session session) throws IOException {
- return fileSystem;
- }
- };
+ super();
}
@Before
public void setUp() throws Exception {
- sshd = SshServer.setUpDefaultServer();
- sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
- sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystemFactory()));
- sshd.setCommandFactory(new ScpCommandFactory());
- sshd.setShellFactory(new EchoShellFactory());
- sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
- sshd.setFileSystemFactory(fileSystemFactory);
- sshd.start();
- port = sshd.getPort();
-
+ setupServer();
JSchLogger.init();
JSch sch = new JSch();
session = sch.getSession("sshd", "localhost", port);
@@ -141,9 +121,7 @@ public class SftpTest extends BaseTestSupport {
session.disconnect();
}
- if (sshd != null) {
- sshd.stop(true);
- }
+ tearDownServer();
}
@Test
@@ -592,104 +570,6 @@ public class SftpTest extends BaseTestSupport {
}
@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();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractCheckFileExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractCheckFileExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractCheckFileExtensionTest.java
new file mode 100644
index 0000000..1927d07
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractCheckFileExtensionTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+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.AbstractSftpClientTestSupport;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.digest.Digest;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.Pair;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSupport {
+ private static final Collection<Integer> DATA_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ Integer.valueOf(Byte.MAX_VALUE),
+ Integer.valueOf(SftpConstants.MIN_CHKFILE_BLOCKSIZE),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Integer.valueOf(Byte.SIZE *IoUtils.DEFAULT_COPY_SIZE)
+ ));
+ private static final Collection<Integer> BLOCK_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ Integer.valueOf(0),
+ Integer.valueOf(SftpConstants.MIN_CHKFILE_BLOCKSIZE),
+ Integer.valueOf(1024),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE)
+ ));
+ @SuppressWarnings("synthetic-access")
+ private static final Collection<Object[]> PARAMETERS =
+ Collections.unmodifiableCollection(new LinkedList<Object[]>() {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ for (NamedFactory<?> factory : BuiltinDigests.VALUES) {
+ String algorithm = factory.getName();
+ for (Number dataSize : DATA_SIZES) {
+ for (Number blockSize : BLOCK_SIZES) {
+ add(new Object[] { algorithm, dataSize, blockSize });
+ }
+ }
+ }
+ }
+ });
+
+ @Parameters(name = "{0} - dataSize={1}, blockSize={2}")
+ public static Collection<Object[]> parameters() {
+ return PARAMETERS;
+ }
+
+ private final String algorithm;
+ private final int dataSize, blockSize;
+
+ public AbstractCheckFileExtensionTest(String algorithm, int dataSize, int blockSize) throws IOException {
+ this.algorithm = algorithm;
+ this.dataSize = dataSize;
+ this.blockSize = blockSize;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tearDownServer();
+ }
+
+ @Test
+ public void testCheckFileExtension() throws Exception {
+ testCheckFileExtension(algorithm, dataSize, blockSize);
+ }
+
+ private void testCheckFileExtension(String expectedAlgorithm, int inputDataSize, int hashBlockSize) throws Exception {
+ NamedFactory<? extends Digest> factory = BuiltinDigests.fromFactoryName(expectedAlgorithm);
+ Digest digest = null;
+ if (blockSize == 0) {
+ digest = factory.create();
+ digest.init();
+ }
+
+ byte[] seed = (getClass().getName() + "#" + getCurrentTestName()
+ + "-" + expectedAlgorithm
+ + "-" + inputDataSize + "/" + hashBlockSize
+ + System.getProperty("line.separator"))
+ .getBytes(StandardCharsets.UTF_8)
+ ;
+
+ try(ByteArrayOutputStream baos=new ByteArrayOutputStream(inputDataSize + seed.length)) {
+ while (baos.size() < inputDataSize) {
+ baos.write(seed);
+
+ if (digest != null) {
+ digest.update(seed);
+ }
+ }
+
+ testCheckFileExtension(factory, baos.toByteArray(), hashBlockSize, (digest == null) ? null : digest.digest());
+ }
+ }
+
+ private void testCheckFileExtension(NamedFactory<? extends Digest> factory, byte[] data, int hashBlockSize, byte[] expectedHash) 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 srcFile = lclSftp.resolve(factory.getName() + "-data-" + data.length + "-" + hashBlockSize + ".txt");
+ Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
+
+ List<String> algorithms = new ArrayList<String>(BuiltinDigests.VALUES.size());
+ // put the selected algorithm 1st and then the rest
+ algorithms.add(factory.getName());
+ for (NamedFactory<? extends Digest> f : BuiltinDigests.VALUES) {
+ if (f == factory) {
+ continue;
+ }
+
+ algorithms.add(f.getName());
+ }
+
+ 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()) {
+ CheckFileNameExtension file = assertExtensionCreated(sftp, CheckFileNameExtension.class);
+ try {
+ Pair<String,?> result = file.checkFileName(srcFolder, algorithms, 0L, 0L, hashBlockSize);
+ fail("Unexpected success to hash folder=" + srcFolder + ": " + result.getFirst());
+ } catch(IOException e) { // expected - not allowed to hash a folder
+ assertTrue("Not an SftpException", e instanceof SftpException);
+ }
+
+ CheckFileHandleExtension hndl = assertExtensionCreated(sftp, CheckFileHandleExtension.class);
+ try(CloseableHandle dirHandle = sftp.openDir(srcFolder)) {
+ try {
+ Pair<String,?> result = hndl.checkFileHandle(dirHandle, algorithms, 0L, 0L, hashBlockSize);
+ fail("Unexpected handle success on folder=" + srcFolder + ": " + result.getFirst());
+ } catch(IOException e) { // expected - not allowed to hash a folder
+ assertTrue("Not an SftpException", e instanceof SftpException);
+ }
+ }
+
+ validateHashResult(file, file.checkFileName(srcPath, algorithms, 0L, 0L, hashBlockSize), algorithms.get(0), expectedHash);
+ try(CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Read)) {
+ validateHashResult(hndl, hndl.checkFileHandle(fileHandle, algorithms, 0L, 0L, hashBlockSize), algorithms.get(0), expectedHash);
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ private void validateHashResult(NamedResource hasher, Pair<String,Collection<byte[]>> result, String expectedAlgorithm, byte[] expectedHash) {
+ String name = hasher.getName();
+ assertNotNull("No result for hash=" + name, result);
+ assertEquals("Mismatched hash algorithms for " + name, expectedAlgorithm, result.getFirst());
+
+ if (GenericUtils.length(expectedHash) > 0) {
+ Collection<byte[]> values = result.getSecond();
+ assertEquals("Mismatched hash values count for " + name, 1, GenericUtils.size(values));
+
+ byte[] actualHash = values.iterator().next();
+ if (!Arrays.equals(expectedHash, actualHash)) {
+ fail("Mismatched hashes for " + name
+ + ": expected=" + BufferUtils.printHex(':', expectedHash)
+ + ", actual=" + BufferUtils.printHex(':', expectedHash));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4818061d/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractMD5HashExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractMD5HashExtensionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractMD5HashExtensionTest.java
new file mode 100644
index 0000000..c5622b0
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/AbstractMD5HashExtensionTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+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.AbstractSftpClientTestSupport;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.common.digest.BuiltinDigests;
+import org.apache.sshd.common.digest.Digest;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport {
+ private static final List<Integer> DATA_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ Integer.valueOf(Byte.MAX_VALUE),
+ Integer.valueOf(SftpConstants.MD5_QUICK_HASH_SIZE),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Integer.valueOf(Byte.SIZE * IoUtils.DEFAULT_COPY_SIZE)
+ ));
+
+ @Parameters(name = "dataSize={0}")
+ public static Collection<Object[]> parameters() {
+ return parameterize(DATA_SIZES);
+ }
+
+ private final int size;
+
+ public AbstractMD5HashExtensionTest(int size) throws IOException {
+ this.size = size;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tearDownServer();
+ }
+
+ @Test
+ public void testMD5HashExtension() throws Exception {
+ testMD5HashExtension(size);
+ }
+
+ private void testMD5HashExtension(int dataSize) throws Exception {
+ byte[] seed = (getClass().getName() + "#" + getCurrentTestName() + "-" + dataSize + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
+ try(ByteArrayOutputStream baos=new ByteArrayOutputStream(dataSize + seed.length)) {
+ while (baos.size() < dataSize) {
+ 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 for file hash on " + srcFolder, 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 for handle hash on " + srcFolder, 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 + " on " + srcFile
+ + ": expected=" + BufferUtils.printHex(':', expectedHash)
+ + ", actual=" + BufferUtils.printHex(':', actualHash));
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+}