You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2018/04/16 11:47:50 UTC
[03/30] mina-sshd git commit: [SSHD-815] Extract SFTP in its own
module
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
new file mode 100644
index 0000000..e29b732
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java
@@ -0,0 +1,510 @@
+/*
+ * 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.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclEntryFlag;
+import java.nio.file.attribute.AclEntryPermission;
+import java.nio.file.attribute.AclEntryType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.OpenMode;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpHelper;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.subsystem.sftp.AbstractSftpEventListenerAdapter;
+import org.apache.sshd.server.subsystem.sftp.DefaultGroupPrincipal;
+import org.apache.sshd.server.subsystem.sftp.SftpEventListener;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystem;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.Utils;
+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;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @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
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class SftpVersionsTest extends AbstractSftpClientTestSupport {
+ private static final List<Integer> VERSIONS =
+ Collections.unmodifiableList(
+ IntStream.rangeClosed(SftpSubsystemEnvironment.LOWER_SFTP_IMPL, SftpSubsystemEnvironment.HIGHER_SFTP_IMPL)
+ .boxed()
+ .collect(Collectors.toList()));
+
+ private final int testVersion;
+
+ public SftpVersionsTest(int version) throws IOException {
+ testVersion = version;
+ }
+
+ @Parameters(name = "version={0}")
+ public static Collection<Object[]> parameters() {
+ return parameterize(VERSIONS);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ public final int getTestedVersion() {
+ return testVersion;
+ }
+
+ @Test // See SSHD-749
+ public void testSftpOpenFlags() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path lclParent = assertHierarchyTargetFolderExists(lclSftp);
+ Path lclFile = lclParent.resolve(getCurrentTestName() + "-" + getTestedVersion() + ".txt");
+ Files.deleteIfExists(lclFile);
+
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, lclFile);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ try (OutputStream out = sftp.write(remotePath, OpenMode.Create, OpenMode.Write)) {
+ out.write(getCurrentTestName().getBytes(StandardCharsets.UTF_8));
+ }
+ assertTrue("File should exist on disk: " + lclFile, Files.exists(lclFile));
+ sftp.remove(remotePath);
+ }
+ }
+ }
+
+ @Test
+ public void testSftpVersionSelector() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ assertEquals("Mismatched negotiated version", getTestedVersion(), sftp.getVersion());
+ }
+ }
+ }
+
+ @Test // see SSHD-572
+ public void testSftpFileTimesUpdate() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path lclFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + "-" + getTestedVersion() + ".txt");
+ Files.write(lclFile, getClass().getName().getBytes(StandardCharsets.UTF_8));
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, lclFile);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ Attributes attrs = sftp.lstat(remotePath);
+ long expectedSeconds = TimeUnit.SECONDS.convert(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1L), TimeUnit.MILLISECONDS);
+ attrs.getFlags().clear();
+ attrs.modifyTime(expectedSeconds);
+ sftp.setStat(remotePath, attrs);
+
+ attrs = sftp.lstat(remotePath);
+ long actualSeconds = attrs.getModifyTime().to(TimeUnit.SECONDS);
+ // The NTFS file system delays updates to the last access time for a file by up to 1 hour after the last access
+ if (expectedSeconds != actualSeconds) {
+ System.err.append("Mismatched last modified time for ").append(lclFile.toString())
+ .append(" - expected=").append(String.valueOf(expectedSeconds))
+ .append('[').append(new Date(TimeUnit.SECONDS.toMillis(expectedSeconds)).toString()).append(']')
+ .append(", actual=").append(String.valueOf(actualSeconds))
+ .append('[').append(new Date(TimeUnit.SECONDS.toMillis(actualSeconds)).toString()).append(']')
+ .println();
+ }
+ }
+ }
+ }
+
+ @Test // see SSHD-573
+ public void testSftpFileTypeAndPermissionsUpdate() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path subFolder = Files.createDirectories(lclSftp.resolve("sub-folder"));
+ String subFolderName = subFolder.getFileName().toString();
+ Path lclFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + "-" + getTestedVersion() + ".txt");
+ String lclFileName = lclFile.getFileName().toString();
+ Files.write(lclFile, getClass().getName().getBytes(StandardCharsets.UTF_8));
+
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, lclSftp);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ for (DirEntry entry : sftp.readDir(remotePath)) {
+ String fileName = entry.getFilename();
+ if (".".equals(fileName) || "..".equals(fileName)) {
+ continue;
+ }
+
+ Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+ if (subFolderName.equals(fileName)) {
+ assertEquals("Mismatched sub-folder type", SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY, attrs.getType());
+ assertTrue("Sub-folder not marked as directory", attrs.isDirectory());
+ } else if (lclFileName.equals(fileName)) {
+ assertEquals("Mismatched sub-file type", SftpConstants.SSH_FILEXFER_TYPE_REGULAR, attrs.getType());
+ assertTrue("Sub-folder not marked as directory", attrs.isRegularFile());
+ }
+ }
+ }
+ }
+ }
+
+ @Test // see SSHD-574
+ public void testSftpACLEncodeDecode() throws Exception {
+ AclEntryType[] types = AclEntryType.values();
+ final List<AclEntry> aclExpected = new ArrayList<>(types.length);
+ for (AclEntryType t : types) {
+ aclExpected.add(AclEntry.newBuilder()
+ .setType(t)
+ .setFlags(EnumSet.allOf(AclEntryFlag.class))
+ .setPermissions(EnumSet.allOf(AclEntryPermission.class))
+ .setPrincipal(new DefaultGroupPrincipal(getCurrentTestName() + "@" + getClass().getPackage().getName()))
+ .build());
+ }
+
+ final AtomicInteger numInvocations = new AtomicInteger(0);
+ SftpSubsystemFactory factory = new SftpSubsystemFactory() {
+ @Override
+ public Command create() {
+ SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(),
+ getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) {
+ @Override
+ protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) throws IOException {
+ NavigableMap<String, Object> attrs = super.resolveFileAttributes(file, flags, options);
+ if (GenericUtils.isEmpty(attrs)) {
+ attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+
+ @SuppressWarnings("unchecked")
+ List<AclEntry> aclActual = (List<AclEntry>) attrs.put("acl", aclExpected);
+ if (aclActual != null) {
+ log.info("resolveFileAttributes(" + file + ") replaced ACL: " + aclActual);
+ }
+ return attrs;
+ }
+
+ @Override
+ protected void setFileAccessControl(Path file, List<AclEntry> aclActual, LinkOption... options) throws IOException {
+ if (aclActual != null) {
+ assertListEquals("Mismatched ACL set for file=" + file, aclExpected, aclActual);
+ numInvocations.incrementAndGet();
+ }
+ }
+ };
+ Collection<? extends SftpEventListener> listeners = getRegisteredListeners();
+ if (GenericUtils.size(listeners) > 0) {
+ for (SftpEventListener l : listeners) {
+ subsystem.addSftpEventListener(l);
+ }
+ }
+
+ return subsystem;
+ }
+ };
+
+ factory.addSftpEventListener(new AbstractSftpEventListenerAdapter() {
+ @Override
+ public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
+ @SuppressWarnings("unchecked")
+ List<AclEntry> aclActual = GenericUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
+ if (getTestedVersion() > SftpConstants.SFTP_V3) {
+ assertListEquals("Mismatched modifying ACL for file=" + path, aclExpected, aclActual);
+ } else {
+ assertNull("Unexpected modifying ACL for file=" + path, aclActual);
+ }
+ }
+
+ @Override
+ public void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ @SuppressWarnings("unchecked")
+ List<AclEntry> aclActual = GenericUtils.isEmpty(attrs) ? null : (List<AclEntry>) attrs.get("acl");
+ if (getTestedVersion() > SftpConstants.SFTP_V3) {
+ assertListEquals("Mismatched modified ACL for file=" + path, aclExpected, aclActual);
+ } else {
+ assertNull("Unexpected modified ACL for file=" + path, aclActual);
+ }
+ }
+ });
+
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Files.createDirectories(lclSftp.resolve("sub-folder"));
+ Path lclFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + "-" + getTestedVersion() + ".txt");
+ Files.write(lclFile, getClass().getName().getBytes(StandardCharsets.UTF_8));
+
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, lclSftp);
+ int numInvoked = 0;
+
+ List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+ sshd.setSubsystemFactories(Collections.singletonList(factory));
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ for (DirEntry entry : sftp.readDir(remotePath)) {
+ String fileName = entry.getFilename();
+ if (".".equals(fileName) || "..".equals(fileName)) {
+ continue;
+ }
+
+ Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+ List<AclEntry> aclActual = attrs.getAcl();
+ if (getTestedVersion() == SftpConstants.SFTP_V3) {
+ assertNull("Unexpected ACL for entry=" + fileName, aclActual);
+ } else {
+ assertListEquals("Mismatched ACL for entry=" + fileName, aclExpected, aclActual);
+ }
+
+ attrs.getFlags().clear();
+ attrs.setAcl(aclExpected);
+ sftp.setStat(remotePath + "/" + fileName, attrs);
+ if (getTestedVersion() > SftpConstants.SFTP_V3) {
+ numInvoked++;
+ }
+ }
+ }
+ } finally {
+ sshd.setSubsystemFactories(factories);
+ }
+
+ assertEquals("Mismatched invocations count", numInvoked, numInvocations.get());
+ }
+
+ @Test // see SSHD-575
+ public void testSftpExtensionsEncodeDecode() throws Exception {
+ final Class<?> anchor = getClass();
+ final Map<String, String> expExtensions = GenericUtils.<String, String>mapBuilder()
+ .put("class", anchor.getSimpleName())
+ .put("package", anchor.getPackage().getName())
+ .put("method", getCurrentTestName())
+ .build();
+
+ final AtomicInteger numInvocations = new AtomicInteger(0);
+ SftpSubsystemFactory factory = new SftpSubsystemFactory() {
+ @Override
+ public Command create() {
+ SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), isShutdownOnExit(),
+ getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) {
+ @Override
+ protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) throws IOException {
+ NavigableMap<String, Object> attrs = super.resolveFileAttributes(file, flags, options);
+ if (GenericUtils.isEmpty(attrs)) {
+ attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ }
+
+ @SuppressWarnings("unchecked")
+ Map<String, String> actExtensions = (Map<String, String>) attrs.put("extended", expExtensions);
+ if (actExtensions != null) {
+ log.info("resolveFileAttributes(" + file + ") replaced extensions: " + actExtensions);
+ }
+ return attrs;
+ }
+
+ @Override
+ protected void setFileExtensions(Path file, Map<String, byte[]> extensions, LinkOption... options) throws IOException {
+ assertExtensionsMapEquals("setFileExtensions(" + file + ")", expExtensions, extensions);
+ numInvocations.incrementAndGet();
+
+ int currentVersion = getTestedVersion();
+ try {
+ super.setFileExtensions(file, extensions, options);
+ assertFalse("Expected exception not generated for version=" + currentVersion, currentVersion >= SftpConstants.SFTP_V6);
+ } catch (UnsupportedOperationException e) {
+ assertTrue("Unexpected exception for version=" + currentVersion, currentVersion >= SftpConstants.SFTP_V6);
+ }
+ }
+ };
+ Collection<? extends SftpEventListener> listeners = getRegisteredListeners();
+ if (GenericUtils.size(listeners) > 0) {
+ for (SftpEventListener l : listeners) {
+ subsystem.addSftpEventListener(l);
+ }
+ }
+
+ return subsystem;
+ }
+ };
+
+ factory.addSftpEventListener(new AbstractSftpEventListenerAdapter() {
+ @Override
+ public void modifyingAttributes(ServerSession session, Path path, Map<String, ?> attrs) {
+ @SuppressWarnings("unchecked")
+ Map<String, byte[]> actExtensions = GenericUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
+ assertExtensionsMapEquals("modifying(" + path + ")", expExtensions, actExtensions);
+ }
+
+ @Override
+ public void modifiedAttributes(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) {
+ @SuppressWarnings("unchecked")
+ Map<String, byte[]> actExtensions = GenericUtils.isEmpty(attrs) ? null : (Map<String, byte[]>) attrs.get("extended");
+ assertExtensionsMapEquals("modified(" + path + ")", expExtensions, actExtensions);
+ }
+ });
+
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Files.createDirectories(lclSftp.resolve("sub-folder"));
+ Path lclFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + "-" + getTestedVersion() + ".txt");
+ Files.write(lclFile, getClass().getName().getBytes(StandardCharsets.UTF_8));
+
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, lclSftp);
+ int numInvoked = 0;
+
+ List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+ sshd.setSubsystemFactories(Collections.singletonList(factory));
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ for (DirEntry entry : sftp.readDir(remotePath)) {
+ String fileName = entry.getFilename();
+ if (".".equals(fileName) || "..".equals(fileName)) {
+ continue;
+ }
+
+ Attributes attrs = validateSftpFileTypeAndPermissions(fileName, getTestedVersion(), entry.getAttributes());
+ Map<String, byte[]> actExtensions = attrs.getExtensions();
+ assertExtensionsMapEquals("dirEntry=" + fileName, expExtensions, actExtensions);
+ attrs.getFlags().clear();
+ attrs.setStringExtensions(expExtensions);
+ sftp.setStat(remotePath + "/" + fileName, attrs);
+ numInvoked++;
+ }
+ }
+ } finally {
+ sshd.setSubsystemFactories(factories);
+ }
+
+ assertEquals("Mismatched invocations count", numInvoked, numInvocations.get());
+ }
+
+ @Test // see SSHD-623
+ public void testEndOfListIndicator() throws Exception {
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session, getTestedVersion())) {
+ AtomicReference<Boolean> eolIndicator = new AtomicReference<>();
+ int version = sftp.getVersion();
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ String remotePath = Utils.resolveRelativeRemotePath(parentPath, targetPath);
+
+ try (CloseableHandle handle = sftp.openDir(remotePath)) {
+ List<DirEntry> entries = sftp.readDir(handle, eolIndicator);
+ for (int index = 1; entries != null; entries = sftp.readDir(handle, eolIndicator), index++) {
+ Boolean value = eolIndicator.get();
+ if (version < SftpConstants.SFTP_V6) {
+ assertNull("Unexpected indicator value at iteration #" + index, value);
+ } else {
+ assertNotNull("No indicator returned at iteration #" + index, value);
+ if (value) {
+ break;
+ }
+ }
+ eolIndicator.set(null); // make sure starting fresh
+ }
+
+ Boolean value = eolIndicator.get();
+ if (version < SftpConstants.SFTP_V6) {
+ assertNull("Unexpected end-of-list indication received at end of entries", value);
+ assertNull("Unexpected no last entries indication", entries);
+ } else {
+ assertNotNull("No end-of-list indication received at end of entries", value);
+ assertNotNull("No last received entries", entries);
+ assertTrue("Bad end-of-list value", value);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + getTestedVersion() + "]";
+ }
+
+ public static void assertExtensionsMapEquals(String message, Map<String, String> expected, Map<String, byte[]> actual) {
+ assertMapEquals(message, expected, SftpHelper.toStringExtensions(actual));
+ }
+
+ private static Attributes validateSftpFileTypeAndPermissions(String fileName, int version, Attributes attrs) {
+ int actualPerms = attrs.getPermissions();
+ if (version == SftpConstants.SFTP_V3) {
+ int expected = SftpHelper.permissionsToFileType(actualPerms);
+ assertEquals(fileName + ": Mismatched file type", expected, attrs.getType());
+ } else {
+ int expected = SftpHelper.fileTypeToPermission(attrs.getType());
+ assertTrue(fileName + ": Missing permision=0x" + Integer.toHexString(expected) + " in 0x" + Integer.toHexString(actualPerms),
+ (actualPerms & expected) == expected);
+ }
+
+ return attrs;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/BuiltinSftpClientExtensionsTest.java
new file mode 100644
index 0000000..e05105d
--- /dev/null
+++ b/sshd-sftp/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.test.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());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtensionTest.java
new file mode 100644
index 0000000..e3537ea
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractCheckFileExtensionTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.helpers;
+
+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.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+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.client.subsystem.sftp.extensions.CheckFileHandleExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.CheckFileNameExtension;
+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.digest.DigestFactory;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
+import org.apache.sshd.common.util.buffer.BufferUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.Utils;
+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;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @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
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSupport {
+ private static final Collection<Integer> DATA_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ (int) Byte.MAX_VALUE,
+ SftpConstants.MIN_CHKFILE_BLOCKSIZE,
+ IoUtils.DEFAULT_COPY_SIZE,
+ Byte.SIZE * IoUtils.DEFAULT_COPY_SIZE
+ ));
+ private static final Collection<Integer> BLOCK_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ 0,
+ SftpConstants.MIN_CHKFILE_BLOCKSIZE,
+ 1024,
+ IoUtils.DEFAULT_COPY_SIZE
+ ));
+ private static final Collection<Object[]> PARAMETERS;
+
+ static {
+ Collection<Object[]> list = new ArrayList<>();
+ for (DigestFactory factory : BuiltinDigests.VALUES) {
+ if (!factory.isSupported()) {
+ System.out.println("Skip unsupported digest=" + factory.getAlgorithm());
+ continue;
+ }
+
+ String algorithm = factory.getName();
+ for (Number dataSize : DATA_SIZES) {
+ for (Number blockSize : BLOCK_SIZES) {
+ list.add(new Object[]{algorithm, dataSize, blockSize});
+ }
+ }
+ }
+ PARAMETERS = list;
+ }
+
+
+ private final String algorithm;
+ private final int dataSize;
+ private final int blockSize;
+
+ public AbstractCheckFileExtensionTest(String algorithm, int dataSize, int blockSize) throws IOException {
+ this.algorithm = algorithm;
+ this.dataSize = dataSize;
+ this.blockSize = blockSize;
+ }
+
+ @Parameters(name = "{0} - dataSize={1}, blockSize={2}")
+ public static Collection<Object[]> parameters() {
+ return PARAMETERS;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @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
+ + IoUtils.EOL)
+ .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());
+ }
+ }
+
+ @SuppressWarnings("checkstyle:nestedtrydepth")
+ private void testCheckFileExtension(NamedFactory<? extends Digest> factory, byte[] data, int hashBlockSize, byte[] expectedHash) throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path srcFile = assertHierarchyTargetFolderExists(lclSftp).resolve(factory.getName() + "-data-" + data.length + "-" + hashBlockSize + ".txt");
+ Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
+
+ List<String> algorithms = new ArrayList<>(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 (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ CheckFileNameExtension file = assertExtensionCreated(sftp, CheckFileNameExtension.class);
+ try {
+ Map.Entry<String, ?> result = file.checkFileName(srcFolder, algorithms, 0L, 0L, hashBlockSize);
+ fail("Unexpected success to hash folder=" + srcFolder + ": " + result.getKey());
+ } 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 {
+ Map.Entry<String, ?> result = hndl.checkFileHandle(dirHandle, algorithms, 0L, 0L, hashBlockSize);
+ fail("Unexpected handle success on folder=" + srcFolder + ": " + result.getKey());
+ } 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);
+ }
+ }
+ }
+ }
+
+ private void validateHashResult(NamedResource hasher, Map.Entry<String, ? extends 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.getKey());
+
+ if (NumberUtils.length(expectedHash) > 0) {
+ Collection<byte[]> values = result.getValue();
+ 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.toHex(':', expectedHash)
+ + ", actual=" + BufferUtils.toHex(':', expectedHash));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtensionTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtensionTest.java
new file mode 100644
index 0000000..ea2783a
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/AbstractMD5HashExtensionTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.helpers;
+
+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.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.client.subsystem.sftp.extensions.MD5FileExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.MD5HandleExtension;
+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.subsystem.sftp.SftpException;
+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.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.Utils;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+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;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @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
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport {
+ private static final List<Integer> DATA_SIZES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ (int) Byte.MAX_VALUE,
+ SftpConstants.MD5_QUICK_HASH_SIZE,
+ IoUtils.DEFAULT_COPY_SIZE,
+ Byte.SIZE * IoUtils.DEFAULT_COPY_SIZE
+ ));
+
+ private final int size;
+
+ public AbstractMD5HashExtensionTest(int size) throws IOException {
+ this.size = size;
+ }
+
+ @Parameters(name = "dataSize={0}")
+ public static Collection<Object[]> parameters() {
+ return parameterize(DATA_SIZES);
+ }
+
+ @BeforeClass
+ public static void checkMD5Supported() {
+ Assume.assumeTrue("MD5 not supported", BuiltinDigests.md5.isSupported());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @Test
+ public void testMD5HashExtension() throws Exception {
+ testMD5HashExtension(size);
+ }
+
+ private void testMD5HashExtension(int dataSize) throws Exception {
+ byte[] seed = (getClass().getName() + "#" + getCurrentTestName() + "-" + dataSize + IoUtils.EOL).getBytes(StandardCharsets.UTF_8);
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize + seed.length)) {
+ while (baos.size() < dataSize) {
+ baos.write(seed);
+ }
+
+ testMD5HashExtension(baos.toByteArray());
+ }
+ }
+
+ @SuppressWarnings("checkstyle:nestedtrydepth")
+ private void testMD5HashExtension(byte[] data) throws Exception {
+ 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 targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path srcFile = assertHierarchyTargetFolderExists(lclSftp).resolve("data-" + data.length + ".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 (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ MD5FileExtension file = assertExtensionCreated(sftp, MD5FileExtension.class);
+ try {
+ byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash);
+ fail("Unexpected file success on folder=" + srcFolder + ": " + BufferUtils.toHex(':', 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.toHex(':', 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.toHex(':', qh)
+ + " using " + type + " on " + srcFile
+ + ": expected=" + BufferUtils.toHex(':', expectedHash)
+ + ", actual=" + BufferUtils.toHex(':', actualHash));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyDataExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyDataExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyDataExtensionImplTest.java
new file mode 100644
index 0000000..01d3334
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyDataExtensionImplTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.helpers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+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.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.client.subsystem.sftp.extensions.CopyDataExtension;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.Utils;
+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;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @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
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class CopyDataExtensionImplTest extends AbstractSftpClientTestSupport {
+ private static final List<Object[]> PARAMETERS =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ new Object[]{
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Integer.valueOf(0),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Long.valueOf(0L)
+ },
+ new Object[]{
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE / 2),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE / 4),
+ Long.valueOf(0L)
+ },
+ new Object[]{
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE / 2),
+ Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE / 4),
+ Long.valueOf(IoUtils.DEFAULT_COPY_SIZE / 2)
+ },
+ new Object[]{
+ Integer.valueOf(Byte.MAX_VALUE),
+ Integer.valueOf(Byte.MAX_VALUE / 2),
+ Integer.valueOf(Byte.MAX_VALUE), // attempt to read more than available
+ Long.valueOf(0L)
+ }
+ ));
+
+ private int size;
+ private int srcOffset;
+ private int length;
+ private long dstOffset;
+
+ public CopyDataExtensionImplTest(int size, int srcOffset, int length, long dstOffset) throws IOException {
+ this.size = size;
+ this.srcOffset = srcOffset;
+ this.length = length;
+ this.dstOffset = dstOffset;
+ }
+
+ @Parameters(name = "size={0}, readOffset={1}, readLength={2}, writeOffset={3}")
+ public static Collection<Object[]> parameters() {
+ return PARAMETERS;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @Test
+ public void testCopyDataExtension() throws Exception {
+ testCopyDataExtension(size, srcOffset, length, dstOffset);
+ }
+
+ private void testCopyDataExtension(int dataSize, int readOffset, int readLength, long writeOffset) throws Exception {
+ byte[] seed = (getClass().getName() + "#" + getCurrentTestName()
+ + "-" + dataSize
+ + "-" + readOffset + "/" + readLength + "/" + writeOffset
+ + IoUtils.EOL)
+ .getBytes(StandardCharsets.UTF_8);
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize + seed.length)) {
+ while (baos.size() < dataSize) {
+ baos.write(seed);
+ }
+
+ testCopyDataExtension(baos.toByteArray(), readOffset, readLength, writeOffset);
+ }
+ }
+
+ private void testCopyDataExtension(byte[] data, int readOffset, int readLength, long writeOffset) throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ LinkOption[] options = IoUtils.getLinkOptions(true);
+ String baseName = readOffset + "-" + readLength + "-" + writeOffset;
+ Path srcFile = assertHierarchyTargetFolderExists(lclSftp, options).resolve(baseName + "-src.txt");
+ Files.write(srcFile, data, IoUtils.EMPTY_OPEN_OPTIONS);
+ String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+
+ Path dstFile = srcFile.getParent().resolve(baseName + "-dst.txt");
+ if (Files.exists(dstFile, options)) {
+ Files.delete(dstFile);
+ }
+ String dstPath = Utils.resolveRelativeRemotePath(parentPath, dstFile);
+ if (writeOffset > 0L) {
+ Factory<? extends Random> factory = client.getRandomFactory();
+ Random randomizer = factory.create();
+ long totalLength = writeOffset + readLength;
+ byte[] workBuf = new byte[(int) Math.min(totalLength, IoUtils.DEFAULT_COPY_SIZE)];
+ try (OutputStream output = Files.newOutputStream(dstFile, IoUtils.EMPTY_OPEN_OPTIONS)) {
+ while (totalLength > 0L) {
+ randomizer.fill(workBuf);
+ output.write(workBuf);
+ totalLength -= workBuf.length;
+ }
+ }
+ }
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ CopyDataExtension ext = assertExtensionCreated(sftp, CopyDataExtension.class);
+ try (CloseableHandle readHandle = sftp.open(srcPath, SftpClient.OpenMode.Read);
+ CloseableHandle writeHandle = sftp.open(dstPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
+ ext.copyData(readHandle, readOffset, readLength, writeHandle, writeOffset);
+ }
+ }
+ }
+
+ int available = data.length;
+ int required = readOffset + readLength;
+ if (required > available) {
+ required = available;
+ }
+ byte[] expected = new byte[required - readOffset];
+ System.arraycopy(data, readOffset, expected, 0, expected.length);
+
+ byte[] actual = new byte[expected.length];
+ try (FileChannel channel = FileChannel.open(dstFile, IoUtils.EMPTY_OPEN_OPTIONS)) {
+ int readLen = channel.read(ByteBuffer.wrap(actual), writeOffset);
+ assertEquals("Mismatched read data size", expected.length, readLen);
+ }
+ assertArrayEquals("Mismatched copy data", expected, actual);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyFileExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyFileExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyFileExtensionImplTest.java
new file mode 100644
index 0000000..b21da13
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/CopyFileExtensionImplTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.helpers;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+
+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.extensions.CopyFileExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.SftpException;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.util.test.Utils;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CopyFileExtensionImplTest extends AbstractSftpClientTestSupport {
+ public CopyFileExtensionImplTest() throws IOException {
+ super();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @Test
+ public void testCopyFileExtension() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Utils.deleteRecursive(lclSftp);
+
+ byte[] data = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+ Path srcFile = assertHierarchyTargetFolderExists(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(true);
+ assertFalse("Destination file unexpectedly exists", Files.exists(dstFile, options));
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ 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);
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
new file mode 100644
index 0000000..0c33113
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.helpers;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+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.extensions.SpaceAvailableExtension;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.SpaceAvailableExtensionInfo;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystem;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.Utils;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSupport {
+ public SpaceAvailableExtensionImplTest() throws IOException {
+ super();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @Test
+ public void testFileStoreReport() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ Path parentPath = targetPath.getParent();
+ FileStore store = Files.getFileStore(lclSftp.getRoot());
+ final String queryPath = Utils.resolveRelativeRemotePath(parentPath, lclSftp);
+ final SpaceAvailableExtensionInfo expected = new SpaceAvailableExtensionInfo(store);
+
+ List<NamedFactory<Command>> factories = sshd.getSubsystemFactories();
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() {
+ @Override
+ public Command create() {
+ return new SftpSubsystem(getExecutorService(), isShutdownOnExit(),
+ getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) {
+ @Override
+ protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException {
+ if (!queryPath.equals(path)) {
+ throw new StreamCorruptedException("Mismatched query paths: expected=" + queryPath + ", actual=" + path);
+ }
+
+ return expected;
+ }
+ };
+ }
+ }));
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ SpaceAvailableExtension ext = assertExtensionCreated(sftp, SpaceAvailableExtension.class);
+ SpaceAvailableExtensionInfo actual = ext.available(queryPath);
+ assertEquals("Mismatched information", expected, actual);
+ }
+ } finally {
+ sshd.setSubsystemFactories(factories);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
new file mode 100644
index 0000000..ac8ed34
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.openssh.helpers;
+
+import java.io.IOException;
+import java.io.StreamCorruptedException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.client.subsystem.sftp.extensions.openssh.OpenSSHFsyncExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatHandleExtension;
+import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.AbstractOpenSSHExtensionParser.OpenSSHExtension;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.FstatVfsExtensionParser;
+import org.apache.sshd.common.subsystem.sftp.extensions.openssh.StatVfsExtensionParser;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystem;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.Utils;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport {
+ public OpenSSHExtensionsTest() throws IOException {
+ super();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ setupServer();
+ }
+
+ @Test
+ public void testFsync() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path srcFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + ".txt");
+ byte[] expected = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8);
+
+ Path parentPath = targetPath.getParent();
+ String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, OpenSSHFsyncExtension.class);
+ try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
+ sftp.write(fileHandle, 0L, expected);
+ fsync.fsync(fileHandle);
+
+ byte[] actual = Files.readAllBytes(srcFile);
+ assertArrayEquals("Mismatched written data", expected, actual);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testStat() throws Exception {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path srcFile = assertHierarchyTargetFolderExists(lclSftp).resolve(getCurrentTestName() + ".txt");
+ Files.write(srcFile, (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8), IoUtils.EMPTY_OPEN_OPTIONS);
+ Path parentPath = targetPath.getParent();
+ String srcPath = Utils.resolveRelativeRemotePath(parentPath, srcFile);
+
+ final AtomicReference<String> extensionHolder = new AtomicReference<>(null);
+ final OpenSSHStatExtensionInfo expected = new OpenSSHStatExtensionInfo();
+ expected.f_bavail = Short.MAX_VALUE;
+ expected.f_bfree = Integer.MAX_VALUE;
+ expected.f_blocks = Short.MAX_VALUE;
+ expected.f_bsize = IoUtils.DEFAULT_COPY_SIZE;
+ expected.f_favail = Long.MAX_VALUE;
+ expected.f_ffree = Byte.MAX_VALUE;
+ expected.f_files = 3777347L;
+ expected.f_flag = OpenSSHStatExtensionInfo.SSH_FXE_STATVFS_ST_RDONLY;
+ expected.f_frsize = 7365L;
+ expected.f_fsid = 1L;
+ expected.f_namemax = 256;
+
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() {
+ @Override
+ public Command create() {
+ return new SftpSubsystem(getExecutorService(), isShutdownOnExit(),
+ getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) {
+ @Override
+ protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) {
+ List<OpenSSHExtension> original = super.resolveOpenSSHExtensions(session);
+ int numOriginal = GenericUtils.size(original);
+ List<OpenSSHExtension> result = new ArrayList<>(numOriginal + 2);
+ if (numOriginal > 0) {
+ result.addAll(original);
+ }
+
+ for (String name : new String[]{StatVfsExtensionParser.NAME, FstatVfsExtensionParser.NAME}) {
+ result.add(new OpenSSHExtension(name, "2"));
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void executeExtendedCommand(Buffer buffer, int id, String extension) throws IOException {
+ if (StatVfsExtensionParser.NAME.equals(extension)
+ || FstatVfsExtensionParser.NAME.equals(extension)) {
+ String prev = extensionHolder.getAndSet(extension);
+ if (prev != null) {
+ throw new StreamCorruptedException("executeExtendedCommand(" + extension + ") previous not null: " + prev);
+ }
+
+ buffer.clear();
+ buffer.putByte((byte) SftpConstants.SSH_FXP_EXTENDED_REPLY);
+ buffer.putInt(id);
+ OpenSSHStatExtensionInfo.encode(buffer, expected);
+ send(buffer);
+ } else {
+ super.executeExtendedCommand(buffer, id, extension);
+ }
+ }
+ };
+ }
+ }));
+
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) {
+ session.addPasswordIdentity(getCurrentTestName());
+ session.auth().verify(5L, TimeUnit.SECONDS);
+
+ try (SftpClient sftp = createSftpClient(session)) {
+ OpenSSHStatPathExtension pathStat = assertExtensionCreated(sftp, OpenSSHStatPathExtension.class);
+ OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath);
+ String invokedExtension = extensionHolder.getAndSet(null);
+ assertEquals("Mismatched invoked extension", pathStat.getName(), invokedExtension);
+ assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
+
+ try (CloseableHandle handle = sftp.open(srcPath)) {
+ OpenSSHStatHandleExtension handleStat = assertExtensionCreated(sftp, OpenSSHStatHandleExtension.class);
+ actual = handleStat.stat(handle);
+ invokedExtension = extensionHolder.getAndSet(null);
+ assertEquals("Mismatched invoked extension", handleStat.getName(), invokedExtension);
+ assertOpenSSHStatExtensionInfoEquals(invokedExtension, expected, actual);
+ }
+ }
+ }
+ }
+ }
+
+ private static void assertOpenSSHStatExtensionInfoEquals(String extension, OpenSSHStatExtensionInfo expected, OpenSSHStatExtensionInfo actual) throws Exception {
+ Field[] fields = expected.getClass().getFields();
+ for (Field f : fields) {
+ String name = f.getName();
+ int mod = f.getModifiers();
+ if (Modifier.isStatic(mod)) {
+ continue;
+ }
+
+ Object expValue = f.get(expected);
+ Object actValue = f.get(actual);
+ assertEquals(extension + "[" + name + "]", expValue, actValue);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpConstantsTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpConstantsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpConstantsTest.java
new file mode 100644
index 0000000..d059d36
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpConstantsTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.subsystem.sftp;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class SftpConstantsTest extends BaseTestSupport {
+ public SftpConstantsTest() {
+ super();
+ }
+
+ @Test
+ public void testRenameModesNotMarkedAsOpcodes() {
+ for (int cmd : new int[]{
+ SftpConstants.SSH_FXP_RENAME_OVERWRITE,
+ SftpConstants.SSH_FXP_RENAME_ATOMIC,
+ SftpConstants.SSH_FXP_RENAME_NATIVE
+ }) {
+ String name = SftpConstants.getCommandMessageName(cmd);
+ assertFalse("Mismatched name for " + cmd + ": " + name, name.startsWith("SSH_FXP_RENAME_"));
+ }
+ }
+
+ @Test
+ public void testRealPathModesNotMarkedAsOpcodes() {
+ for (int cmd = SftpConstants.SSH_FXP_REALPATH_NO_CHECK; cmd <= SftpConstants.SSH_FXP_REALPATH_STAT_IF; cmd++) {
+ String name = SftpConstants.getCommandMessageName(cmd);
+ assertFalse("Mismatched name for " + cmd + ": " + name, name.startsWith("SSH_FXP_REALPATH_"));
+ }
+ }
+
+ @Test
+ public void testSubstatusNameResolution() {
+ for (int status = SftpConstants.SSH_FX_OK; status <= SftpConstants.SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK; status++) {
+ String name = SftpConstants.getStatusName(status);
+ assertTrue("Failed to convert status=" + status + ": " + name, name.startsWith("SSH_FX_"));
+ }
+ }
+
+ @Test
+ public void testSubstatusMessageResolution() {
+ for (int status = SftpConstants.SSH_FX_OK; status <= SftpConstants.SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK; status++) {
+ String message = SftpHelper.resolveStatusMessage(status);
+ assertTrue("Missing message for status=" + status, GenericUtils.isNotEmpty(message));
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/251db9b9/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpUniversalOwnerAndGroupTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpUniversalOwnerAndGroupTest.java b/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpUniversalOwnerAndGroupTest.java
new file mode 100644
index 0000000..704aa05
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/common/subsystem/sftp/SftpUniversalOwnerAndGroupTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.common.subsystem.sftp;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+public class SftpUniversalOwnerAndGroupTest extends BaseTestSupport {
+ public SftpUniversalOwnerAndGroupTest() {
+ super();
+ }
+
+ @Test
+ public void testNameFormat() {
+ for (SftpUniversalOwnerAndGroup value : SftpUniversalOwnerAndGroup.VALUES) {
+ String name = value.getName();
+ assertFalse(value.name() + ": empty name", GenericUtils.isEmpty(name));
+ assertTrue(value.name() + ": bad suffix", name.charAt(name.length() - 1) == '@');
+
+ for (int index = 0; index < name.length() - 1; index++) {
+ char ch = name.charAt(index);
+ if ((ch < 'A') || (ch > 'Z')) {
+ fail("Non-uppercase character in " + name);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testFromName() {
+ for (String name : new String[]{null, "", getCurrentTestName()}) {
+ assertNull("Unexpected value for '" + name + "'", SftpUniversalOwnerAndGroup.fromName(name));
+ }
+
+ for (SftpUniversalOwnerAndGroup expected : SftpUniversalOwnerAndGroup.VALUES) {
+ String name = expected.getName();
+ for (int index = 0; index < name.length(); index++) {
+ assertSame(name, expected, SftpUniversalOwnerAndGroup.fromName(name));
+ name = shuffleCase(name);
+ }
+ }
+ }
+}