http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java new file mode 100644 index 0000000..98c0205 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java @@ -0,0 +1,139 @@ +/* + * 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.server.subsystem.sftp; + +import java.util.concurrent.ExecutorService; + +import org.apache.sshd.common.subsystem.sftp.SftpConstants; +import org.apache.sshd.common.util.ObjectBuilder; +import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.subsystem.SubsystemFactory; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class SftpSubsystemFactory implements SubsystemFactory, Cloneable, ExecutorServiceConfigurer { + public static final String NAME = SftpConstants.SFTP_SUBSYSTEM_NAME; + public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn; + + public static class Builder implements ObjectBuilder<SftpSubsystemFactory> { + private final SftpSubsystemFactory factory = new SftpSubsystemFactory(); + + public Builder() { + super(); + } + + public Builder withExecutorService(ExecutorService service) { + factory.setExecutorService(service); + return this; + } + + public Builder withShutdownOnExit(boolean shutdown) { + factory.setShutdownOnExit(shutdown); + return this; + } + + public Builder withUnsupportedAttributePolicy(UnsupportedAttributePolicy p) { + factory.setUnsupportedAttributePolicy(p); + return this; + } + + @Override + public SftpSubsystemFactory build() { + // return a clone so that each invocation returns a different instance - avoid shared instances + return factory.clone(); + } + } + + private ExecutorService executors; + private boolean shutdownExecutor; + private UnsupportedAttributePolicy policy = DEFAULT_POLICY; + + public SftpSubsystemFactory() { + super(); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public ExecutorService getExecutorService() { + return executors; + } + + /** + * @param service The {@link ExecutorService} to be used by the {@link SftpSubsystem} + * command when starting execution. If {@code null} then a single-threaded ad-hoc service is used. + */ + @Override + public void setExecutorService(ExecutorService service) { + executors = service; + } + + @Override + public boolean isShutdownOnExit() { + return shutdownExecutor; + } + + /** + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when subsystem terminates - unless it is the ad-hoc service, which + * will be shutdown regardless + */ + @Override + public void setShutdownOnExit(boolean shutdownOnExit) { + shutdownExecutor = shutdownOnExit; + } + + public UnsupportedAttributePolicy getUnsupportedAttributePolicy() { + return policy; + } + + /** + * @param p The {@link UnsupportedAttributePolicy} to use if failed to access + * some local file attributes + */ + public void setUnsupportedAttributePolicy(UnsupportedAttributePolicy p) { + if (p == null) { + throw new IllegalArgumentException("No policy provided"); + } + + policy = p; + } + + @Override + public Command create() { + return new SftpSubsystem(getExecutorService(), isShutdownOnExit(), getUnsupportedAttributePolicy()); + } + + @Override + public SftpSubsystemFactory clone() { + try { + return getClass().cast(super.clone()); // shallow clone is good enough + } catch (CloneNotSupportedException e) { + throw new UnsupportedOperationException("Unexpected clone exception", e); // unexpected since we implement cloneable + } + } + + +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java new file mode 100644 index 0000000..87a0c01 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/UnsupportedAttributePolicy.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.sshd.server.subsystem.sftp; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Set; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum UnsupportedAttributePolicy { + Ignore, + Warn, + ThrowException; + + public static final Set<UnsupportedAttributePolicy> VALUES = + Collections.unmodifiableSet(EnumSet.allOf(UnsupportedAttributePolicy.class)); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java index 96f69b8..4ec4891 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java @@ -70,7 +70,7 @@ import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.session.AbstractSession; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.Session; -import org.apache.sshd.common.sftp.SftpConstants; +import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.Transformer; import org.apache.sshd.common.util.buffer.Buffer; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java deleted file mode 100644 index 22f1a70..0000000 --- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/DefaultCloseableHandleTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.sshd.client.sftp; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.sshd.client.sftp.SftpClient.CloseableHandle; -import org.apache.sshd.client.sftp.SftpClient.Handle; -import org.apache.sshd.util.BaseTestSupport; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; -import org.mockito.Matchers; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class DefaultCloseableHandleTest extends BaseTestSupport { - public DefaultCloseableHandleTest() { - super(); - } - - @Test - public void testChannelBehavior() throws IOException { - final String id = getCurrentTestName(); - SftpClient client = Mockito.mock(SftpClient.class); - Mockito.doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - Handle handle = (Handle) args[0]; - assertEquals("Mismatched closing handle", id, handle.id); - return null; - } - }).when(client).close(Matchers.any(Handle.class)); - - CloseableHandle handle = new DefaultCloseableHandle(client, id); - try { - assertTrue("Handle not initially open", handle.isOpen()); - } finally { - handle.close(); - } - assertFalse("Handle not marked as closed", handle.isOpen()); - // make sure close was called - Mockito.verify(client).close(handle); - } - - @Test - public void testCloseIdempotent() throws IOException { - SftpClient client = Mockito.mock(SftpClient.class); - final AtomicBoolean closeCalled = new AtomicBoolean(false); - Mockito.doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true)); - return null; - } - }).when(client).close(Matchers.any(Handle.class)); - - CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName()); - for (int index=0; index < Byte.SIZE; index++) { - handle.close(); - } - - assertTrue("Close method not called", closeCalled.get()); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java deleted file mode 100644 index cbe27a8..0000000 --- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpFileSystemTest.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.sshd.client.sftp; - -import java.io.IOException; -import java.net.URI; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.OverlappingFileLockException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.nio.file.attribute.GroupPrincipal; -import java.nio.file.attribute.PosixFilePermissions; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.attribute.UserPrincipalNotFoundException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - -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.common.sftp.SftpConstants; -import org.apache.sshd.common.util.OsUtils; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.server.Command; -import org.apache.sshd.server.SshServer; -import org.apache.sshd.server.command.ScpCommandFactory; -import org.apache.sshd.server.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; -import org.junit.After; -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class SftpFileSystemTest extends BaseTestSupport { - - private SshServer sshd; - private int port; - private final FileSystemFactory fileSystemFactory; - - public SftpFileSystemTest() 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; - } - }; - } - - @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(); - } - - @After - public void tearDown() throws Exception { - if (sshd != null) { - sshd.stop(true); - } - } - - @Test - public void testFileSystem() throws IOException { - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - - try(FileSystem fs = FileSystems.newFileSystem( - URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"), - new TreeMap<String,Object>() { - private static final long serialVersionUID = 1L; // we're not serializing it - - { - put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE)); - put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE)); - } - })) { - - Iterable<Path> rootDirs = fs.getRootDirectories(); - for (Path root : rootDirs) { - String rootName = root.toString(); - try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) { - for (Path child : ds) { - System.out.append('\t').append('[').append(rootName).append("] ").println(child); - } - } catch(IOException | RuntimeException e) { - // TODO on Windows one might get share problems for *.sys files - // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process" - // for now, Windows is less of a target so we are lenient with it - if (OsUtils.isWin32()) { - System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage()); - } else { - throw e; - } - } - } - - Path current = fs.getPath(".").toRealPath().normalize(); - System.out.append("CWD: ").println(current); - - Path parentPath = targetPath.getParent(); - Path clientFolder = lclSftp.resolve("client"); - String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt")); - Path file1 = fs.getPath(remFile1Path); - Files.createDirectories(file1.getParent()); - - String expected="Hello world: " + getCurrentTestName(); - { - Files.write(file1, expected.getBytes()); - String buf = new String(Files.readAllBytes(file1)); - assertEquals("Mismatched read test data", expected, buf); - } - - String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt")); - Path file2 = fs.getPath(remFile2Path); - String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt")); - Path file3 = fs.getPath(remFile3Path); - try { - Files.move(file2, file3); - fail("Unexpected success in moving " + file2 + " => " + file3); - } catch (NoSuchFileException e) { - // expected - } - - Files.write(file2, "h".getBytes()); - try { - Files.move(file1, file2); - fail("Unexpected success in moving " + file1 + " => " + file2); - } catch (FileAlreadyExistsException e) { - // expected - } - Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); - Files.move(file2, file1); - - Map<String, Object> attrs = Files.readAttributes(file1, "*"); - System.out.append(file1.toString()).append(" attributes: ").println(attrs); - - // TODO: symbolic links only work for absolute files - // Path link = fs.getPath("target/sftp/client/test2.txt"); - // Files.createSymbolicLink(link, link.relativize(file)); - // assertTrue(Files.isSymbolicLink(link)); - // assertEquals("test.txt", Files.readSymbolicLink(link).toString()); - - // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest - if (OsUtils.isUNIX()) { - Path link = fs.getPath(remFile2Path); - Path linkParent = link.getParent(); - Path relPath = linkParent.relativize(file1); - Files.createSymbolicLink(link, relPath); - assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link)); - - Path symLink = Files.readSymbolicLink(link); - assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString()); - Files.delete(link); - } - - attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS); - System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs); - - assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1))); - - try (FileChannel channel = FileChannel.open(file1)) { - try (FileLock lock = channel.lock()) { - System.out.println("Locked " + lock.toString()); - - try (FileChannel channel2 = FileChannel.open(file1)) { - try (FileLock lock2 = channel2.lock()) { - System.out.println("Locked " + lock2.toString()); - fail("Unexpected success in re-locking " + file1); - } catch (OverlappingFileLockException e) { - // expected - } - } - } - } - - Files.delete(file1); - } - } - - @Test - public void testAttributes() throws IOException { - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - - try(FileSystem fs = FileSystems.newFileSystem( - URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"), - new TreeMap<String,Object>() { - private static final long serialVersionUID = 1L; // we're not serializing it - - { - put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE)); - put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE)); - } - })) { - - Path parentPath = targetPath.getParent(); - Path clientFolder = lclSftp.resolve("client"); - String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt")); - Path file = fs.getPath(remFilePath); - Files.createDirectories(file.getParent()); - Files.write(file, (getCurrentTestName() + "\n").getBytes()); - - Map<String, Object> attrs = Files.readAttributes(file, "posix:*"); - assertNotNull("NO attributes read for " + file, attrs); - - Files.setAttribute(file, "basic:size", Long.valueOf(2L)); - Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----")); - Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L)); - - FileSystem fileSystem = file.getFileSystem(); - try { - UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService(); - GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone"); - Files.setAttribute(file, "posix:group", group); - } catch (UserPrincipalNotFoundException e) { - // Also, according to the Javadoc: - // "Where an implementation does not support any notion of - // group then this method always throws UserPrincipalNotFoundException." - // Therefore we are lenient with this exception for Windows - if (OsUtils.isWin32()) { - System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage()); - } else { - throw e; - } - } - } - } - - @Test - public void testRootFileSystem() throws IOException { - Path targetPath = detectTargetFolder().toPath(); - Path rootNative = targetPath.resolve("root").toAbsolutePath(); - Utils.deleteRecursive(rootNative); - Files.createDirectories(rootNative); - - try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) { - Path dir = Files.createDirectories(fs.getPath("test/foo")); - System.out.println("Created " + dir); - } - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java deleted file mode 100644 index ad6f603..0000000 --- a/sshd-core/src/test/java/org/apache/sshd/client/sftp/SftpTest.java +++ /dev/null @@ -1,651 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.sshd.client.sftp; - -import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_FILE_ALREADY_EXISTS; -import static org.apache.sshd.common.sftp.SftpConstants.SSH_FX_NO_SUCH_FILE; -import static org.apache.sshd.common.sftp.SftpConstants.S_IRUSR; -import static org.apache.sshd.common.sftp.SftpConstants.S_IWUSR; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Random; -import java.util.Vector; -import java.util.concurrent.TimeUnit; - -import org.apache.sshd.client.SshClient; -import org.apache.sshd.client.session.ClientSession; -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.common.sftp.SftpConstants; -import org.apache.sshd.common.util.OsUtils; -import org.apache.sshd.common.util.buffer.ByteArrayBuffer; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.server.Command; -import org.apache.sshd.server.SshServer; -import org.apache.sshd.server.command.ScpCommandFactory; -import org.apache.sshd.server.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.JSchLogger; -import org.apache.sshd.util.SimpleUserInfo; -import org.apache.sshd.util.Utils; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runners.MethodSorters; - -import com.jcraft.jsch.ChannelSftp; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.SftpException; - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class SftpTest extends BaseTestSupport { - - 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; - } - }; - } - - @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(); - - JSchLogger.init(); - JSch sch = new JSch(); - session = sch.getSession("sshd", "localhost", port); - session.setUserInfo(new SimpleUserInfo("sshd")); - session.connect(); - } - - @After - public void tearDown() throws Exception { - if (session != null) { - session.disconnect(); - } - - if (sshd != null) { - sshd.stop(true); - } - } - - @Test - @Ignore - public void testExternal() throws Exception { - System.out.println("SFTP subsystem available on port " + port); - Thread.sleep(5 * 60000); - } - - @Test - public void testOpen() throws Exception { - 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); - - Path targetPath = detectTargetFolder().toPath(); - Path parentPath = targetPath.getParent(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Path clientFolder = lclSftp.resolve("client"); - Path testFile = clientFolder.resolve(getCurrentTestName() + ".txt"); - String file = Utils.resolveRelativeRemotePath(parentPath, testFile); - - File javaFile = testFile.toFile(); - assertHierarchyTargetFolderExists(javaFile.getParentFile()); - javaFile.createNewFile(); - javaFile.setWritable(false, false); - javaFile.setReadable(false, false); - - try (SftpClient sftp = session.createSftpClient()) { - boolean isWindows = OsUtils.isWin32(); - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) { - // NOTE: on Windows files are always readable - // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java - assertTrue("Empty read should have failed on " + file, isWindows); - } catch (IOException e) { - if (isWindows) { - throw e; - } - } - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - fail("Empty write should have failed on " + file); - } catch (IOException e) { - // ok - } - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) { - // NOTE: on Windows files are always readable - assertTrue("Empty truncate should have failed on " + file, isWindows); - } catch (IOException e) { - // ok - } - - // NOTE: on Windows files are always readable - int perms=sftp.stat(file).perms; - int permsMask=S_IWUSR | (isWindows ? 0 : S_IRUSR); - assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, (perms & permsMask)); - - javaFile.setWritable(true, false); - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) { - // OK should succeed - assertTrue("Handle not marked as open for file=" + file, h.isOpen()); - } - - byte[] d = "0123456789\n".getBytes(); - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - sftp.write(h, 0, d, 0, d.length); - sftp.write(h, d.length, d, 0, d.length); - } - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - sftp.write(h, d.length * 2, d, 0, d.length); - } - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - sftp.write(h, 3, "-".getBytes(), 0, 1); - } - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) { - // NOTE: on Windows files are always readable - assertTrue("Data read should have failed on " + file, isWindows); - } catch (IOException e) { - if (isWindows) { - throw e; - } - } - - javaFile.setReadable(true, false); - - byte[] buf = new byte[3]; - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read))) { - int l = sftp.read(h, 2l, buf, 0, 3); - assertEquals("Mismatched read data", "2-4", new String(buf, 0, l)); - } - } - } finally { - client.stop(); - } - } - } - - @Test - public void testClient() throws Exception { - 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); - - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - Files.createDirectories(lclSftp); - - Path parentPath = targetPath.getParent(); - Path clientFolder = lclSftp.resolve("client"); - String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder); - String file = dir + "/" + getCurrentTestName() + ".txt"; - - try (SftpClient sftp = session.createSftpClient()) { - sftp.mkdir(dir); - - try(SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) { - byte[] d = "0123456789\n".getBytes(); - sftp.write(h, 0, d, 0, d.length); - sftp.write(h, d.length, d, 0, d.length); - - SftpClient.Attributes attrs = sftp.stat(h); - assertNotNull("No handle attributes", attrs); - } - - try(SftpClient.CloseableHandle h = sftp.openDir(dir)) { - SftpClient.DirEntry[] dirEntries = sftp.readDir(h); - assertNotNull("No dir entries", dirEntries); - assertEquals("Mismatced number of dir entries", 1, dirEntries.length); - assertNull("Unexpected entry read", sftp.readDir(h)); - } - - sftp.remove(file); - - byte[] workBuf = new byte[IoUtils.DEFAULT_COPY_SIZE * Short.SIZE]; - new Random(System.currentTimeMillis()).nextBytes(workBuf); - try (OutputStream os = sftp.write(file)) { - os.write(workBuf); - } - - try (InputStream is = sftp.read(file, IoUtils.DEFAULT_COPY_SIZE)) { - int readLen = is.read(workBuf); - assertEquals("Mismatched read data length", workBuf.length, readLen); - - int i = is.read(); - assertEquals("Unexpected read past EOF", -1, i); - } - - SftpClient.Attributes attributes = sftp.stat(file); - assertTrue("Test file not detected as regular", attributes.isRegularFile()); - - attributes = sftp.stat(dir); - assertTrue("Test directory not reported as such", attributes.isDirectory()); - - int nb = 0; - for (SftpClient.DirEntry entry : sftp.readDir(dir)) { - assertNotNull("Unexpected null entry", entry); - nb++; - } - assertEquals("Mismatched read dir entries", 1, nb); - - sftp.remove(file); - - sftp.rmdir(dir); - } - } finally { - client.stop(); - } - } - } - - /** - * this test is meant to test out write's logic, to ensure that internal chunking (based on Buffer.MAX_LEN) is - * functioning properly. To do this, we write a variety of file sizes, both smaller and larger than Buffer.MAX_LEN. - * in addition, this test ensures that improper arguments passed in get caught with an IllegalArgumentException - * @throws Exception upon any uncaught exception or failure - */ - @Test - public void testWriteChunking() throws Exception { - 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); - - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - Files.createDirectories(lclSftp); - - Path parentPath = targetPath.getParent(); - Path clientFolder = lclSftp.resolve("client"); - String dir = Utils.resolveRelativeRemotePath(parentPath, clientFolder); - - try(SftpClient sftp = session.createSftpClient()) { - sftp.mkdir(dir); - - uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN - 1, "bufferMaxLenMinusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN, "bufferMaxLenFile.txt"); - // were chunking not implemented, these would fail. these sizes should invoke our internal chunking mechanism - uploadAndVerifyFile(sftp, clientFolder, dir, ByteArrayBuffer.MAX_LEN + 1, "bufferMaxLenPlusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, (int)(1.5 * ByteArrayBuffer.MAX_LEN), "1point5BufferMaxLenFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) - 1, "2TimesBufferMaxLenMinusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, 2 * ByteArrayBuffer.MAX_LEN, "2TimesBufferMaxLenFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, (2 * ByteArrayBuffer.MAX_LEN) + 1, "2TimesBufferMaxLenPlusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt"); - - // test erroneous calls that check for negative values - Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid"); - testInvalidParams(sftp, invalidPath, Utils.resolveRelativeRemotePath(parentPath, invalidPath)); - - // cleanup - sftp.rmdir(dir); - } - } finally { - client.stop(); - } - } - } - - private void testInvalidParams(SftpClient sftp, Path file, String filePath) throws Exception { - // generate random file and upload it - String randomData = randomString(5); - byte[] randomBytes = randomData.getBytes(); - try(SftpClient.CloseableHandle handle = sftp.open(filePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) { - try { - sftp.write(handle, -1, randomBytes, 0, 0); - fail("should not have been able to write file with invalid file offset for " + filePath); - } catch (IllegalArgumentException e) { - // expected - } - try { - sftp.write(handle, 0, randomBytes, -1, 0); - fail("should not have been able to write file with invalid source offset for " + filePath); - } catch (IllegalArgumentException e) { - // expected - } - try { - sftp.write(handle, 0, randomBytes, 0, -1); - fail("should not have been able to write file with invalid length for " + filePath); - } catch (IllegalArgumentException e) { - // expected - } - try { - sftp.write(handle, 0, randomBytes, 0, randomBytes.length + 1); - fail("should not have been able to write file with length bigger than array itself (no offset) for " + filePath); - } catch (IllegalArgumentException e) { - // expected - } - try { - sftp.write(handle, 0, randomBytes, randomBytes.length, 1); - fail("should not have been able to write file with length bigger than array itself (with offset) for " + filePath); - } catch (IllegalArgumentException e) { - // expected - } - } - - sftp.remove(filePath); - assertFalse("File should not be there: " + file.toString(), Files.exists(file)); - } - - private void uploadAndVerifyFile(SftpClient sftp, Path clientFolder, String remoteDir, int size, String filename) throws Exception { - // generate random file and upload it - String remotePath = remoteDir + "/" + filename; - String randomData = randomString(size); - try(SftpClient.CloseableHandle handle = sftp.open(remotePath, EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create))) { - sftp.write(handle, 0, randomData.getBytes(), 0, randomData.length()); - } - - // verify results - Path resultPath = clientFolder.resolve(filename); - assertTrue("File should exist on disk: " + resultPath, Files.exists(resultPath)); - assertTrue("Mismatched file contents: " + resultPath, randomData.equals(readFile(remotePath))); - - // cleanup - sftp.remove(remotePath); - assertFalse("File should have been removed: " + resultPath, Files.exists(resultPath)); - } - - @Test - public void testSftp() throws Exception { - String d = getCurrentTestName() + "\n"; - - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - Files.createDirectories(lclSftp); - - Path target = lclSftp.resolve(getCurrentTestName() + ".txt"); - String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), target); - - final int NUM_ITERATIONS=10; - StringBuilder sb = new StringBuilder(d.length() * NUM_ITERATIONS * NUM_ITERATIONS); - for (int j = 1; j <= NUM_ITERATIONS; j++) { - if (sb.length() > 0) { - sb.setLength(0); - } - - for (int i = 0; i < j; i++) { - sb.append(d); - } - - sendFile(remotePath, sb.toString()); - assertFileLength(target, sb.length(), 5000); - Files.delete(target); - } - } - - @Test - public void testReadWriteWithOffset() 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 localPath = lclSftp.resolve(getCurrentTestName() + ".txt"); - String remotePath = Utils.resolveRelativeRemotePath(targetPath.getParent(), localPath); - String data = getCurrentTestName(); - String extraData = "@" + getClass().getSimpleName(); - int appendOffset = -5; - - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - try { - c.put(new ByteArrayInputStream(data.getBytes()), remotePath); - - assertTrue("Remote file not created after initial write: " + localPath, Files.exists(localPath)); - assertEquals("Mismatched data read from " + remotePath, data, readFile(remotePath)); - - try(OutputStream os = c.put(remotePath, null, ChannelSftp.APPEND, appendOffset)) { - os.write(extraData.getBytes()); - } - } finally { - c.disconnect(); - } - - assertTrue("Remote file not created after data update: " + localPath, Files.exists(localPath)); - - String expected = data.substring(0, data.length() + appendOffset) + extraData; - String actual = readFile(remotePath); - assertEquals("Mismatched final file data in " + remotePath, expected, actual); - } - - @Test - public void testReadDir() throws Exception { - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - try { - URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI(); - URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI(); - String path = new File(base.relativize(url).getPath()).getParent() + "/"; - path = path.replace('\\', '/'); - Vector<?> res = c.ls(path); - for (Object f : res) { - System.out.println(f.toString()); - } - } finally { - c.disconnect(); - } - } - - @Test - public void testRealPath() throws Exception { - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - - try { - URI url = getClass().getClassLoader().getResource(SshClient.class.getName().replace('.', '/') + ".class").toURI(); - URI base = new File(System.getProperty("user.dir")).getAbsoluteFile().toURI(); - String path = new File(base.relativize(url).getPath()).getParent() + "/"; - path = path.replace('\\', '/'); - String real = c.realpath(path); - System.out.println(real); - try { - real = c.realpath(path + "/foobar"); - System.out.println(real); - fail("Expected SftpException"); - } catch (SftpException e) { - // ok - } - } finally { - c.disconnect(); - } - } - - @Test - public void testRename() throws Exception { - 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); - - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - Files.createDirectories(lclSftp); - - Path parentPath = targetPath.getParent(); - Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client")); - - try(SftpClient sftp = session.createSftpClient()) { - Path file1 = clientFolder.resolve(getCurrentTestName() + "-1.txt"); - String file1Path = Utils.resolveRelativeRemotePath(parentPath, file1); - try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) { - os.write((getCurrentTestName() + "\n").getBytes()); - } - - Path file2 = clientFolder.resolve(getCurrentTestName() + "-2.txt"); - String file2Path = Utils.resolveRelativeRemotePath(parentPath, file2); - Path file3 = clientFolder.resolve(getCurrentTestName() + "-3.txt"); - String file3Path = Utils.resolveRelativeRemotePath(parentPath, file3); - try { - sftp.rename(file2Path, file3Path); - fail("Unxpected rename success of " + file2Path + " => " + file3Path); - } catch (org.apache.sshd.client.SftpException e) { - assertEquals("Mismatched status for failed rename of " + file2Path + " => " + file3Path, SSH_FX_NO_SUCH_FILE, e.getStatus()); - } - - try (OutputStream os = sftp.write(file2Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) { - os.write("H".getBytes()); - } - - try { - sftp.rename(file1Path, file2Path); - fail("Unxpected rename success of " + file1Path + " => " + file2Path); - } catch (org.apache.sshd.client.SftpException e) { - assertEquals("Mismatched status for failed rename of " + file1Path + " => " + file2Path, SSH_FX_FILE_ALREADY_EXISTS, e.getStatus()); - } - - sftp.rename(file1Path, file2Path, SftpClient.CopyMode.Overwrite); - } - } finally { - client.stop(); - } - } - } - - @Test - public void testCreateSymbolicLink() throws Exception { - // Do not execute on windows as the file system does not support symlinks - Assume.assumeTrue("Skip non-Unix O/S", OsUtils.isUNIX()); - - Path targetPath = detectTargetFolder().toPath(); - Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - Utils.deleteRecursive(lclSftp); - Files.createDirectories(lclSftp); - - Path parentPath = targetPath.getParent(); - Path sourcePath = lclSftp.resolve(getCurrentTestName() + ".txt"); - String remSrcPath = Utils.resolveRelativeRemotePath(parentPath, sourcePath); - Path linkPath = lclSftp.resolve("link-" + sourcePath.getFileName()); - String remLinkPath = Utils.resolveRelativeRemotePath(parentPath, linkPath); - - String data = getCurrentTestName(); - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - try { - c.put(new ByteArrayInputStream(data.getBytes()), remSrcPath); - - assertTrue("Source file not created: " + sourcePath, Files.exists(sourcePath)); - assertEquals("Mismatched stored data in " + remSrcPath, data, readFile(remSrcPath)); - - c.symlink(remSrcPath, remLinkPath); - - assertTrue("Symlink not created: " + linkPath, Files.exists(linkPath)); - assertEquals("Mismatche link data in " + remLinkPath, data, readFile(remLinkPath)); - - String str1 = c.readlink(remLinkPath); - String str2 = c.realpath(remSrcPath); - assertEquals("Mismatched link vs. real path", str1, str2); - } finally { - c.disconnect(); - } - } - - protected String readFile(String path) throws Exception { - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - - try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); - InputStream is = c.get(path)) { - - byte[] buffer = new byte[256]; - int count; - while (-1 != (count = is.read(buffer))) { - bos.write(buffer, 0, count); - } - - return bos.toString(); - } finally { - c.disconnect(); - } - } - - protected void sendFile(String path, String data) throws Exception { - ChannelSftp c = (ChannelSftp) session.openChannel(SftpConstants.SFTP_SUBSYSTEM_NAME); - c.connect(); - try { - c.put(new ByteArrayInputStream(data.getBytes()), path); - } finally { - c.disconnect(); - } - } - - private String randomString(int size) { - StringBuilder sb = new StringBuilder(size); - for (int i = 0; i < size; i++) { - sb.append((char) ((i % 10) + '0')); - } - return sb.toString(); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java new file mode 100644 index 0000000..8dbab78 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandleTest.java @@ -0,0 +1,92 @@ +/* + * 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.util.concurrent.atomic.AtomicBoolean; + +import org.apache.sshd.client.subsystem.sftp.DefaultCloseableHandle; +import org.apache.sshd.client.subsystem.sftp.SftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; +import org.apache.sshd.client.subsystem.sftp.SftpClient.Handle; +import org.apache.sshd.util.BaseTestSupport; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class DefaultCloseableHandleTest extends BaseTestSupport { + public DefaultCloseableHandleTest() { + super(); + } + + @Test + public void testChannelBehavior() throws IOException { + final String id = getCurrentTestName(); + SftpClient client = Mockito.mock(SftpClient.class); + Mockito.doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + Handle handle = (Handle) args[0]; + assertEquals("Mismatched closing handle", id, handle.id); + return null; + } + }).when(client).close(Matchers.any(Handle.class)); + + CloseableHandle handle = new DefaultCloseableHandle(client, id); + try { + assertTrue("Handle not initially open", handle.isOpen()); + } finally { + handle.close(); + } + assertFalse("Handle not marked as closed", handle.isOpen()); + // make sure close was called + Mockito.verify(client).close(handle); + } + + @Test + public void testCloseIdempotent() throws IOException { + SftpClient client = Mockito.mock(SftpClient.class); + final AtomicBoolean closeCalled = new AtomicBoolean(false); + Mockito.doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + assertFalse("Close already called on handle=" + args[0], closeCalled.getAndSet(true)); + return null; + } + }).when(client).close(Matchers.any(Handle.class)); + + CloseableHandle handle = new DefaultCloseableHandle(client, getCurrentTestName()); + for (int index=0; index < Byte.SIZE; index++) { + handle.close(); + } + + assertTrue("Close method not called", closeCalled.get()); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a6e2bf9e/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java new file mode 100644 index 0000000..fe3a123 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java @@ -0,0 +1,288 @@ +/* + * 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.net.URI; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.attribute.UserPrincipalNotFoundException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.sshd.client.subsystem.sftp.SftpClient; +import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider; +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.common.subsystem.sftp.SftpConstants; +import org.apache.sshd.common.util.OsUtils; +import org.apache.sshd.common.util.io.IoUtils; +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; +import org.junit.After; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class SftpFileSystemTest extends BaseTestSupport { + + private SshServer sshd; + private int port; + private final FileSystemFactory fileSystemFactory; + + public SftpFileSystemTest() 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; + } + }; + } + + @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(); + } + + @After + public void tearDown() throws Exception { + if (sshd != null) { + sshd.stop(true); + } + } + + @Test + public void testFileSystem() throws IOException { + Path targetPath = detectTargetFolder().toPath(); + Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); + Utils.deleteRecursive(lclSftp); + + try(FileSystem fs = FileSystems.newFileSystem( + URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"), + new TreeMap<String,Object>() { + private static final long serialVersionUID = 1L; // we're not serializing it + + { + put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE)); + put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(IoUtils.DEFAULT_COPY_SIZE)); + } + })) { + + Iterable<Path> rootDirs = fs.getRootDirectories(); + for (Path root : rootDirs) { + String rootName = root.toString(); + try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) { + for (Path child : ds) { + System.out.append('\t').append('[').append(rootName).append("] ").println(child); + } + } catch(IOException | RuntimeException e) { + // TODO on Windows one might get share problems for *.sys files + // e.g. "C:\hiberfil.sys: The process cannot access the file because it is being used by another process" + // for now, Windows is less of a target so we are lenient with it + if (OsUtils.isWin32()) { + System.err.println(e.getClass().getSimpleName() + " while accessing children of root=" + root + ": " + e.getMessage()); + } else { + throw e; + } + } + } + + Path current = fs.getPath(".").toRealPath().normalize(); + System.out.append("CWD: ").println(current); + + Path parentPath = targetPath.getParent(); + Path clientFolder = lclSftp.resolve("client"); + String remFile1Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-1.txt")); + Path file1 = fs.getPath(remFile1Path); + Files.createDirectories(file1.getParent()); + + String expected="Hello world: " + getCurrentTestName(); + { + Files.write(file1, expected.getBytes()); + String buf = new String(Files.readAllBytes(file1)); + assertEquals("Mismatched read test data", expected, buf); + } + + String remFile2Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-2.txt")); + Path file2 = fs.getPath(remFile2Path); + String remFile3Path = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + "-3.txt")); + Path file3 = fs.getPath(remFile3Path); + try { + Files.move(file2, file3); + fail("Unexpected success in moving " + file2 + " => " + file3); + } catch (NoSuchFileException e) { + // expected + } + + Files.write(file2, "h".getBytes()); + try { + Files.move(file1, file2); + fail("Unexpected success in moving " + file1 + " => " + file2); + } catch (FileAlreadyExistsException e) { + // expected + } + Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); + Files.move(file2, file1); + + Map<String, Object> attrs = Files.readAttributes(file1, "*"); + System.out.append(file1.toString()).append(" attributes: ").println(attrs); + + // TODO: symbolic links only work for absolute files + // Path link = fs.getPath("target/sftp/client/test2.txt"); + // Files.createSymbolicLink(link, link.relativize(file)); + // assertTrue(Files.isSymbolicLink(link)); + // assertEquals("test.txt", Files.readSymbolicLink(link).toString()); + + // TODO there are many issues with Windows and symbolic links - for now they are of a lesser interest + if (OsUtils.isUNIX()) { + Path link = fs.getPath(remFile2Path); + Path linkParent = link.getParent(); + Path relPath = linkParent.relativize(file1); + Files.createSymbolicLink(link, relPath); + assertTrue("Not a symbolic link: " + link, Files.isSymbolicLink(link)); + + Path symLink = Files.readSymbolicLink(link); + assertEquals("mismatched symbolic link name", relPath.toString(), symLink.toString()); + Files.delete(link); + } + + attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS); + System.out.append(file1.toString()).append(" no-follow attributes: ").println(attrs); + + assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1))); + + try (FileChannel channel = FileChannel.open(file1)) { + try (FileLock lock = channel.lock()) { + System.out.println("Locked " + lock.toString()); + + try (FileChannel channel2 = FileChannel.open(file1)) { + try (FileLock lock2 = channel2.lock()) { + System.out.println("Locked " + lock2.toString()); + fail("Unexpected success in re-locking " + file1); + } catch (OverlappingFileLockException e) { + // expected + } + } + } + } + + Files.delete(file1); + } + } + + @Test + public void testAttributes() throws IOException { + Path targetPath = detectTargetFolder().toPath(); + Path lclSftp = Utils.resolve(targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); + Utils.deleteRecursive(lclSftp); + + try(FileSystem fs = FileSystems.newFileSystem( + URI.create("sftp://" + getCurrentTestName() + ":" + getCurrentTestName() + "@localhost:" + port + "/"), + new TreeMap<String,Object>() { + private static final long serialVersionUID = 1L; // we're not serializing it + + { + put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_READ_BUFFER_SIZE)); + put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, Integer.valueOf(SftpClient.MIN_WRITE_BUFFER_SIZE)); + } + })) { + + Path parentPath = targetPath.getParent(); + Path clientFolder = lclSftp.resolve("client"); + String remFilePath = Utils.resolveRelativeRemotePath(parentPath, clientFolder.resolve(getCurrentTestName() + ".txt")); + Path file = fs.getPath(remFilePath); + Files.createDirectories(file.getParent()); + Files.write(file, (getCurrentTestName() + "\n").getBytes()); + + Map<String, Object> attrs = Files.readAttributes(file, "posix:*"); + assertNotNull("NO attributes read for " + file, attrs); + + Files.setAttribute(file, "basic:size", Long.valueOf(2L)); + Files.setAttribute(file, "posix:permissions", PosixFilePermissions.fromString("rwxr-----")); + Files.setAttribute(file, "basic:lastModifiedTime", FileTime.fromMillis(100000L)); + + FileSystem fileSystem = file.getFileSystem(); + try { + UserPrincipalLookupService userLookupService = fileSystem.getUserPrincipalLookupService(); + GroupPrincipal group = userLookupService.lookupPrincipalByGroupName("everyone"); + Files.setAttribute(file, "posix:group", group); + } catch (UserPrincipalNotFoundException e) { + // Also, according to the Javadoc: + // "Where an implementation does not support any notion of + // group then this method always throws UserPrincipalNotFoundException." + // Therefore we are lenient with this exception for Windows + if (OsUtils.isWin32()) { + System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage()); + } else { + throw e; + } + } + } + } + + @Test + public void testRootFileSystem() throws IOException { + Path targetPath = detectTargetFolder().toPath(); + Path rootNative = targetPath.resolve("root").toAbsolutePath(); + Utils.deleteRecursive(rootNative); + Files.createDirectories(rootNative); + + try(FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) { + Path dir = Files.createDirectories(fs.getPath("test/foo")); + System.out.println("Created " + dir); + } + } +}
