You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/11 16:50:49 UTC
[1/8] mina-sshd git commit: [SSHD-861] Add support for password-less
login via SftpFileSystemProvider
Repository: mina-sshd
Updated Branches:
refs/heads/master c64bd7a95 -> 44576a59a
[SSHD-861] Add support for password-less login via SftpFileSystemProvider
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/44576a59
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/44576a59
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/44576a59
Branch: refs/heads/master
Commit: 44576a59a54df08ee28400bdac4ed40642623dd4
Parents: e3b8acd
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 11:27:36 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200
----------------------------------------------------------------------
README.md | 8 +++++++-
.../SftpFileSystemClientSessionInitializer.java | 8 ++++++--
.../sftp/fs/SftpFileSystemProvider.java | 20 ++++++++++++++------
.../sftp/fs/SftpFileSystemURITest.java | 10 ++++++++++
4 files changed, 37 insertions(+), 9 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index f8bfa56..720c3e9 100644
--- a/README.md
+++ b/README.md
@@ -1028,7 +1028,13 @@ the (default) password-based one:
public void authenticateClientSession(
SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
throws IOException {
- // Set up password-less login instead of password-based
+ /*
+ * Set up password-less login instead of password-based using the specified key
+ *
+ * Note: if SSH client and/or session already have a KeyPairProvider set up and the code
+ * knows that these keys are already registered with the remote server, then no need to
+ * add the public key identitiy - can simply call sesssion.auth().verify(context.getMaxAuthTime()).
+ */
KeyPair kp = ... obtain a registered key-pair...
session.addPublicKeyIdentity(kp);
return sesssion.auth().verify(context.getMaxAuthTime());
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
index 576e302..3524110 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
@@ -73,8 +73,12 @@ public interface SftpFileSystemClientSessionInitializer {
default void authenticateClientSession(
SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
throws IOException {
- PasswordHolder password = context.getCredentials();
- session.addPasswordIdentity(password.getPassword());
+ PasswordHolder passwordHolder = context.getCredentials();
+ String password = passwordHolder.getPassword();
+ // If no password provided perhaps the client is set-up to use registered public keys
+ if (password != null) {
+ session.addPasswordIdentity(password);
+ }
session.auth().verify(context.getMaxAuthTime());
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
index fdec2f3..5acf6da 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
@@ -354,9 +354,12 @@ public class SftpFileSystemProvider extends FileSystemProvider {
return null;
}
- String[] ui = GenericUtils.split(userInfo, ':');
- ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
- return new BasicCredentialsImpl(ui[0], ui[1]);
+ int pos = userInfo.indexOf(':');
+ if (pos < 0) {
+ return new BasicCredentialsImpl(userInfo, null); // assume password-less login
+ }
+
+ return new BasicCredentialsImpl(userInfo.substring(0, pos), userInfo.substring(pos + 1));
}
public static Map<String, Object> parseURIParameters(URI uri) {
@@ -1320,7 +1323,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
public static String encodeCredentials(String username, String password) {
ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
- ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+
/*
* There is no way to properly encode/decode credentials that already contain
* colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
@@ -1334,7 +1337,12 @@ public class SftpFileSystemProvider extends FileSystemProvider {
* reject such data when it is received as part of a reference and
* should reject the storage of such data in unencrypted form.
*/
- ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
- return username + ":" + password;
+ ValidateUtils.checkTrue((username.indexOf(':') < 0) && ((password == null) || (password.indexOf(':') < 0)),
+ "Reserved character used in credentials");
+ if (password == null) {
+ return username; // assume password-less login required
+ } else {
+ return username + ":" + password;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/44576a59/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
index a8bfaf8..02eb835 100644
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
@@ -88,6 +88,7 @@ public class SftpFileSystemURITest extends JUnitTestSupport {
}
});
add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+ add(new Object[] {"19.65.7.3", 0, "user", null, null});
}
};
}
@@ -108,6 +109,15 @@ public class SftpFileSystemURITest extends JUnitTestSupport {
assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
}
+ @Test
+ public void testEncodeDecodeCredentials() {
+ String userInfo = SftpFileSystemProvider.encodeCredentials(username, password);
+ BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(userInfo);
+ assertNotNull("No credentials provided", credentials);
+ assertEquals("Mismatched user", username, credentials.getUsername());
+ assertEquals("Mismatched password", password, credentials.getPassword());
+ }
+
@Override
public String toString() {
return getClass().getSimpleName()
[7/8] mina-sshd git commit: [SSHD-861] Added
SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Posted by lg...@apache.org.
[SSHD-861] Added SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/e3b8acd7
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/e3b8acd7
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/e3b8acd7
Branch: refs/heads/master
Commit: e3b8acd7e068fdc586f1f825eb675a335eb42824
Parents: c24635d
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 10:43:16 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200
----------------------------------------------------------------------
CHANGES.md | 4 +
README.md | 34 +-
.../apache/sshd/cli/client/SftpCommandMain.java | 2 +-
.../java.nio.file.spi.FileSystemProvider | 2 +-
.../sftp/SftpAclFileAttributeView.java | 67 -
.../subsystem/sftp/SftpClientFactory.java | 16 +-
.../subsystem/sftp/SftpDirectoryStream.java | 65 -
.../client/subsystem/sftp/SftpFileStore.java | 105 --
.../client/subsystem/sftp/SftpFileSystem.java | 600 --------
.../subsystem/sftp/SftpFileSystemChannel.java | 37 -
.../subsystem/sftp/SftpFileSystemProvider.java | 1312 -----------------
.../sshd/client/subsystem/sftp/SftpPath.java | 43 -
.../client/subsystem/sftp/SftpPathIterator.java | 82 --
.../sftp/SftpPosixFileAttributeView.java | 94 --
.../subsystem/sftp/SftpPosixFileAttributes.java | 113 --
.../sftp/fs/SftpAclFileAttributeView.java | 68 +
.../subsystem/sftp/fs/SftpDirectoryStream.java | 67 +
.../client/subsystem/sftp/fs/SftpFileStore.java | 105 ++
.../subsystem/sftp/fs/SftpFileSystem.java | 604 ++++++++
.../sftp/fs/SftpFileSystemChannel.java | 40 +
.../SftpFileSystemClientSessionInitializer.java | 97 ++
.../fs/SftpFileSystemInitializationContext.java | 142 ++
.../sftp/fs/SftpFileSystemProvider.java | 1340 ++++++++++++++++++
.../sshd/client/subsystem/sftp/fs/SftpPath.java | 43 +
.../subsystem/sftp/fs/SftpPathIterator.java | 84 ++
.../sftp/fs/SftpPosixFileAttributeView.java | 95 ++
.../sftp/fs/SftpPosixFileAttributes.java | 113 ++
.../impl/AbstractSftpFileAttributeView.java | 6 +-
.../sftp/impl/DefaultSftpClientFactory.java | 4 +-
.../subsystem/sftp/SftpFileSystemTest.java | 497 -------
.../subsystem/sftp/SftpFileSystemURITest.java | 121 --
.../subsystem/sftp/fs/SftpFileSystemTest.java | 532 +++++++
.../sftp/fs/SftpFileSystemURITest.java | 121 ++
33 files changed, 3503 insertions(+), 3152 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index f909f44..2a338cc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,6 +27,10 @@ user to try and repeat an encrypted private key decoding using a different passw
* `SshAgent#getIdentities` returns an `Iterable` rather than a `List`
+* `SftpFileSystemProvider` and its associated helper classes have been moved to
+`org.apache.sshd.client.subsystem.sftp.fs` package
+
+* Added `SftpFileSystemClientSessionInitializer` support in `SftpFileSystemProvider`
## Behavioral changes
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index c5edc53..f8bfa56 100644
--- a/README.md
+++ b/README.md
@@ -957,9 +957,16 @@ system.
It is highly recommended to `close()` the mounted file system once no longer necessary in order to release the
associated SFTP session sooner rather than later - e.g., via a `try-with-resource` code block.
+**Caveat:** Due to URI encoding of the username/password as a basic authentication, the system currently
+does not allow colon (`:`) in either one in order to avoid parsing confusion. See [RFC 3986 - section 3.2.1](https://tools.ietf.org/html/rfc3986#section-3.2.1):
+
+>> Use of the format "user:password" in the userinfo field is
+>> deprecated ... Applications may choose to ignore or reject such
+>> data when it is received as part of a reference...
+
#### Configuring the `SftpFileSystemProvider`
-When "mounting" a new file system one can provide configuration parameters using either the
+When "mounting" a new file system one can provide extra configuration parameters using either the
environment map in the [FileSystems#newFileSystem](https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileSystems.html#newFileSystem)
method or via the URI query parameters. See the `SftpFileSystemProvider` for the available
configuration keys and values.
@@ -1005,6 +1012,31 @@ configuration keys and values.
```
+#### Configuring the client session used to create an `SftpFileSystem`
+
+It is possible to register a `SftpFileSystemClientSessionInitializer` with the provider instead of the default one
+and thus better control the `ClientSession` used to generate the file-system instance. The default implementation
+simply connects and authenticates before creating a default `SftpFileSystem` instance. Users may wish
+to override some options or provide their own - e.g., execute a password-less authentication instead of
+the (default) password-based one:
+
+```java
+
+ SftpFileSystemProvider provider = ... obtain/create a provider ...
+ provider.setSftpFileSystemClientSessionInitializer(new SftpFileSystemClientSessionInitializer() {
+ @Override
+ public void authenticateClientSession(
+ SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
+ throws IOException {
+ // Set up password-less login instead of password-based
+ KeyPair kp = ... obtain a registered key-pair...
+ session.addPublicKeyIdentity(kp);
+ return sesssion.auth().verify(context.getMaxAuthTime());
+ }
+ });
+
+```
+
#### Tracking accessed location via `SftpFileSystemAccessor`
One can override the default `SftpFileSystemAccessor` and thus be able to track all opened files and folders
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index e730448..99427ff 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -47,9 +47,9 @@ import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.ServiceFactory;
import org.apache.sshd.common.channel.ChannelFactory;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
index 75fea68..6d92ee2 100644
--- a/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
+++ b/sshd-sftp/src/main/filtered-resources/META-INF/services/java.nio.file.spi.FileSystemProvider
@@ -17,4 +17,4 @@
## under the License.
##
-org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider
+org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
deleted file mode 100644
index 7cada6e..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpAclFileAttributeView.java
+++ /dev/null
@@ -1,67 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.List;
-
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpAclFileAttributeView extends AbstractSftpFileAttributeView implements AclFileAttributeView {
- public SftpAclFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
- super(provider, path, options);
- }
-
- @Override
- public UserPrincipal getOwner() throws IOException {
- PosixFileAttributes v = provider.readAttributes(path, PosixFileAttributes.class, options);
- return v.owner();
- }
-
- @Override
- public void setOwner(UserPrincipal owner) throws IOException {
- provider.setAttribute(path, "posix", "owner", owner, options);
- }
-
- @Override
- public String name() {
- return "acl";
- }
-
- @Override
- public List<AclEntry> getAcl() throws IOException {
- return readRemoteAttributes().getAcl();
- }
-
- @Override
- public void setAcl(List<AclEntry> acl) throws IOException {
- writeRemoteAttributes(new SftpClient.Attributes().acl(acl));
- }
-
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
index 5497c91..0672482 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpClientFactory.java
@@ -19,9 +19,9 @@
package org.apache.sshd.client.subsystem.sftp;
import java.io.IOException;
-import java.nio.file.FileSystem;
import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
import org.apache.sshd.client.subsystem.sftp.impl.DefaultSftpClientFactory;
/**
@@ -67,23 +67,23 @@ public interface SftpClientFactory {
*/
SftpClient createSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException;
- default FileSystem createSftpFileSystem(ClientSession session) throws IOException {
+ default SftpFileSystem createSftpFileSystem(ClientSession session) throws IOException {
return createSftpFileSystem(session, SftpVersionSelector.CURRENT);
}
- default FileSystem createSftpFileSystem(ClientSession session, int version) throws IOException {
+ default SftpFileSystem createSftpFileSystem(ClientSession session, int version) throws IOException {
return createSftpFileSystem(session, SftpVersionSelector.fixedVersionSelector(version));
}
- default FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+ default SftpFileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
return createSftpFileSystem(session, selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE);
}
- default FileSystem createSftpFileSystem(ClientSession session, int version, int readBufferSize, int writeBufferSize) throws IOException {
+ default SftpFileSystem createSftpFileSystem(ClientSession session, int version, int readBufferSize, int writeBufferSize) throws IOException {
return createSftpFileSystem(session, SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize);
}
- default FileSystem createSftpFileSystem(ClientSession session, int readBufferSize, int writeBufferSize) throws IOException {
+ default SftpFileSystem createSftpFileSystem(ClientSession session, int readBufferSize, int writeBufferSize) throws IOException {
return createSftpFileSystem(session, SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize);
}
@@ -92,10 +92,10 @@ public interface SftpClientFactory {
* @param selector The {@link SftpVersionSelector} to use in order to negotiate the SFTP version
* @param readBufferSize Default I/O read buffer size
* @param writeBufferSize Default I/O write buffer size
- * @return The created {@link FileSystem} instance
+ * @return The created {@link SftpFileSystem} instance
* @throws IOException If failed to create the instance
*/
- FileSystem createSftpFileSystem(
+ SftpFileSystem createSftpFileSystem(
ClientSession session, SftpVersionSelector selector, int readBufferSize, int writeBufferSize)
throws IOException;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
deleted file mode 100644
index 5f48966..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpDirectoryStream.java
+++ /dev/null
@@ -1,65 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Path;
-import java.util.Iterator;
-
-/**
- * Implements a remote {@link DirectoryStream}
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpDirectoryStream implements DirectoryStream<Path> {
- private final SftpClient sftp;
- private final Iterable<SftpClient.DirEntry> iter;
- private final SftpPath p;
-
- /**
- * @param path The remote {@link SftpPath}
- * @throws IOException If failed to initialize the directory access handle
- */
- public SftpDirectoryStream(SftpPath path) throws IOException {
- SftpFileSystem fs = path.getFileSystem();
- p = path;
- sftp = fs.getClient();
- iter = sftp.readDir(path.toString());
- }
-
- /**
- * Client instance used to access the remote directory
- *
- * @return The {@link SftpClient} instance used to access the remote directory
- */
- public final SftpClient getClient() {
- return sftp;
- }
-
- @Override
- public Iterator<Path> iterator() {
- return new SftpPathIterator(p, iter);
- }
-
- @Override
- public void close() throws IOException {
- sftp.close();
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
deleted file mode 100644
index 8a6f1f1..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileStore.java
+++ /dev/null
@@ -1,105 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.util.Collection;
-
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileStore extends FileStore {
- private final SftpFileSystem fs;
- private final String name;
-
- public SftpFileStore(String name, SftpFileSystem fs) {
- this.name = name;
- this.fs = fs;
- }
-
- public final SftpFileSystem getFileSystem() {
- return fs;
- }
-
- @Override
- public String name() {
- return name;
- }
-
- @Override
- public String type() {
- return SftpConstants.SFTP_SUBSYSTEM_NAME;
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public long getTotalSpace() throws IOException {
- return Long.MAX_VALUE; // TODO use SFTPv6 space-available extension
- }
-
- @Override
- public long getUsableSpace() throws IOException {
- return Long.MAX_VALUE;
- }
-
- @Override
- public long getUnallocatedSpace() throws IOException {
- return Long.MAX_VALUE;
- }
-
- @Override
- public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
- SftpFileSystem sftpFs = getFileSystem();
- SftpFileSystemProvider provider = sftpFs.provider();
- return provider.isSupportedFileAttributeView(sftpFs, type);
- }
-
- @Override
- public boolean supportsFileAttributeView(String name) {
- if (GenericUtils.isEmpty(name)) {
- return false; // debug breakpoint
- }
-
- FileSystem sftpFs = getFileSystem();
- Collection<String> views = sftpFs.supportedFileAttributeViews();
- return !GenericUtils.isEmpty(views) && views.contains(name);
- }
-
- @Override
- public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
- return null; // no special views supported
- }
-
- @Override
- public Object getAttribute(String attribute) throws IOException {
- return null; // no special attributes supported
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
deleted file mode 100644
index ed16a26..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystem.java
+++ /dev/null
@@ -1,600 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.StreamCorruptedException;
-import java.nio.charset.Charset;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystemException;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.NavigableMap;
-import java.util.NavigableSet;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.channel.ClientChannel;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.session.ClientSessionHolder;
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient;
-import org.apache.sshd.common.file.util.BaseFileSystem;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.buffer.Buffer;
-
-public class SftpFileSystem extends BaseFileSystem<SftpPath> implements ClientSessionHolder {
- public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
- public static final int DEFAULT_POOL_SIZE = 8;
-
- public static final NavigableSet<String> UNIVERSAL_SUPPORTED_VIEWS =
- Collections.unmodifiableNavigableSet(
- GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "basic", "posix", "owner"));
-
- private final String id;
- private final ClientSession clientSession;
- private final SftpClientFactory factory;
- private final SftpVersionSelector selector;
- private final Queue<SftpClient> pool;
- private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
- private final int version;
- private final Set<String> supportedViews;
- private SftpPath defaultDir;
- private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
- private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
- private final List<FileStore> stores;
-
- public SftpFileSystem(
- SftpFileSystemProvider provider, String id, ClientSession session,
- SftpClientFactory factory, SftpVersionSelector selector)
- throws IOException {
- super(provider);
- this.id = id;
- this.clientSession = Objects.requireNonNull(session, "No client session");
- this.factory = factory != null ? factory : SftpClientFactory.instance();
- this.selector = selector;
- this.stores = Collections.unmodifiableList(Collections.<FileStore>singletonList(new SftpFileStore(id, this)));
- this.pool = new LinkedBlockingQueue<>(session.getIntProperty(POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
- try (SftpClient client = getClient()) {
- version = client.getVersion();
- defaultDir = getPath(client.canonicalPath("."));
- }
-
- if (version >= SftpConstants.SFTP_V4) {
- Set<String> views = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
- views.addAll(UNIVERSAL_SUPPORTED_VIEWS);
- views.add("acl");
- supportedViews = Collections.unmodifiableSet(views);
- } else {
- supportedViews = UNIVERSAL_SUPPORTED_VIEWS;
- }
- }
-
- public final SftpVersionSelector getSftpVersionSelector() {
- return selector;
- }
-
- public final String getId() {
- return id;
- }
-
- public final int getVersion() {
- return version;
- }
-
- @Override
- public SftpFileSystemProvider provider() {
- return (SftpFileSystemProvider) super.provider();
- }
-
- @Override // NOTE: co-variant return
- public List<FileStore> getFileStores() {
- return this.stores;
- }
-
- public int getReadBufferSize() {
- return readBufferSize;
- }
-
- public void setReadBufferSize(int size) {
- if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
- }
-
- readBufferSize = size;
- }
-
- public int getWriteBufferSize() {
- return writeBufferSize;
- }
-
- public void setWriteBufferSize(int size) {
- if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
- throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
- }
-
- writeBufferSize = size;
- }
-
- @Override
- protected SftpPath create(String root, List<String> names) {
- return new SftpPath(this, root, names);
- }
-
- @Override
- public ClientSession getClientSession() {
- return clientSession;
- }
-
- @SuppressWarnings("synthetic-access")
- public SftpClient getClient() throws IOException {
- Wrapper wrapper = wrappers.get();
- if (wrapper == null) {
- while (wrapper == null) {
- SftpClient client = pool.poll();
- if (client == null) {
- ClientSession session = getClientSession();
- client = factory.createSftpClient(session, getSftpVersionSelector());
- }
- if (!client.isClosing()) {
- wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
- }
- }
- wrappers.set(wrapper);
- } else {
- wrapper.increment();
- }
- return wrapper;
- }
-
- @Override
- public void close() throws IOException {
- if (isOpen()) {
- SftpFileSystemProvider provider = provider();
- String fsId = getId();
- SftpFileSystem fs = provider.removeFileSystem(fsId);
- ClientSession session = getClientSession();
- session.close(true);
-
- if ((fs != null) && (fs != this)) {
- throw new FileSystemException(fsId, fsId, "Mismatched FS instance for id=" + fsId);
- }
- }
- }
-
- @Override
- public boolean isOpen() {
- ClientSession session = getClientSession();
- return session.isOpen();
- }
-
- @Override
- public Set<String> supportedFileAttributeViews() {
- return supportedViews;
- }
-
- @Override
- public UserPrincipalLookupService getUserPrincipalLookupService() {
- return DefaultUserPrincipalLookupService.INSTANCE;
- }
-
- @Override
- public SftpPath getDefaultDir() {
- return defaultDir;
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "[" + getClientSession() + "]";
- }
-
- private final class Wrapper extends AbstractSftpClient {
- private final SftpClient delegate;
- private final AtomicInteger count = new AtomicInteger(1);
- private final int readSize;
- private final int writeSize;
-
- private Wrapper(SftpClient delegate, int readSize, int writeSize) {
- this.delegate = delegate;
- this.readSize = readSize;
- this.writeSize = writeSize;
- }
-
- @Override
- public int getVersion() {
- return delegate.getVersion();
- }
-
- @Override
- public ClientSession getClientSession() {
- return delegate.getClientSession();
- }
-
- @Override
- public ClientChannel getClientChannel() {
- return delegate.getClientChannel();
- }
-
- @Override
- public NavigableMap<String, byte[]> getServerExtensions() {
- return delegate.getServerExtensions();
- }
-
- @Override
- public Charset getNameDecodingCharset() {
- return delegate.getNameDecodingCharset();
- }
-
- @Override
- public void setNameDecodingCharset(Charset cs) {
- delegate.setNameDecodingCharset(cs);
- }
-
- @Override
- public boolean isClosing() {
- return false;
- }
-
- @Override
- public boolean isOpen() {
- return count.get() > 0;
- }
-
- @SuppressWarnings("synthetic-access")
- @Override
- public void close() throws IOException {
- if (count.decrementAndGet() <= 0) {
- if (!pool.offer(delegate)) {
- delegate.close();
- }
- wrappers.set(null);
- }
- }
-
- public void increment() {
- count.incrementAndGet();
- }
-
- @Override
- public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
- if (!isOpen()) {
- throw new IOException("open(" + path + ")[" + options + "] client is closed");
- }
- return delegate.open(path, options);
- }
-
- @Override
- public void close(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("close(" + handle + ") client is closed");
- }
- delegate.close(handle);
- }
-
- @Override
- public void remove(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("remove(" + path + ") client is closed");
- }
- delegate.remove(path);
- }
-
- @Override
- public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
- if (!isOpen()) {
- throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
- }
- delegate.rename(oldPath, newPath, options);
- }
-
- @Override
- public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
- }
- return delegate.read(handle, fileOffset, dst, dstOffset, len);
- }
-
- @Override
- public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
- if (!isOpen()) {
- throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
- }
- delegate.write(handle, fileOffset, src, srcOffset, len);
- }
-
- @Override
- public void mkdir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("mkdir(" + path + ") client is closed");
- }
- delegate.mkdir(path);
- }
-
- @Override
- public void rmdir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("rmdir(" + path + ") client is closed");
- }
- delegate.rmdir(path);
- }
-
- @Override
- public CloseableHandle openDir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("openDir(" + path + ") client is closed");
- }
- return delegate.openDir(path);
- }
-
- @Override
- public List<DirEntry> readDir(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("readDir(" + handle + ") client is closed");
- }
- return delegate.readDir(handle);
- }
-
- @Override
- public Iterable<DirEntry> listDir(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("readDir(" + handle + ") client is closed");
- }
- return delegate.listDir(handle);
- }
-
- @Override
- public String canonicalPath(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("canonicalPath(" + path + ") client is closed");
- }
- return delegate.canonicalPath(path);
- }
-
- @Override
- public Attributes stat(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("stat(" + path + ") client is closed");
- }
- return delegate.stat(path);
- }
-
- @Override
- public Attributes lstat(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("lstat(" + path + ") client is closed");
- }
- return delegate.lstat(path);
- }
-
- @Override
- public Attributes stat(Handle handle) throws IOException {
- if (!isOpen()) {
- throw new IOException("stat(" + handle + ") client is closed");
- }
- return delegate.stat(handle);
- }
-
- @Override
- public void setStat(String path, Attributes attributes) throws IOException {
- if (!isOpen()) {
- throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
- }
- delegate.setStat(path, attributes);
- }
-
- @Override
- public void setStat(Handle handle, Attributes attributes) throws IOException {
- if (!isOpen()) {
- throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
- }
- delegate.setStat(handle, attributes);
- }
-
- @Override
- public String readLink(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("readLink(" + path + ") client is closed");
- }
- return delegate.readLink(path);
- }
-
- @Override
- public void symLink(String linkPath, String targetPath) throws IOException {
- if (!isOpen()) {
- throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
- }
- delegate.symLink(linkPath, targetPath);
- }
-
- @Override
- public Iterable<DirEntry> readDir(String path) throws IOException {
- if (!isOpen()) {
- throw new IOException("readDir(" + path + ") client is closed");
- }
- return delegate.readDir(path);
- }
-
- @Override
- public InputStream read(String path) throws IOException {
- return read(path, readSize);
- }
-
- @Override
- public InputStream read(String path, OpenMode... mode) throws IOException {
- return read(path, readSize, mode);
- }
-
- @Override
- public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
- return read(path, readSize, mode);
- }
-
- @Override
- public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
- if (!isOpen()) {
- throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
- }
- return delegate.read(path, bufferSize, mode);
- }
-
- @Override
- public OutputStream write(String path) throws IOException {
- return write(path, writeSize);
- }
-
- @Override
- public OutputStream write(String path, OpenMode... mode) throws IOException {
- return write(path, writeSize, mode);
- }
-
- @Override
- public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
- return write(path, writeSize, mode);
- }
-
- @Override
- public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
- if (!isOpen()) {
- throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
- }
- return delegate.write(path, bufferSize, mode);
- }
-
- @Override
- public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
- if (!isOpen()) {
- throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
- }
- delegate.link(linkPath, targetPath, symbolic);
- }
-
- @Override
- public void lock(Handle handle, long offset, long length, int mask) throws IOException {
- if (!isOpen()) {
- throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
- }
- delegate.lock(handle, offset, length, mask);
- }
-
- @Override
- public void unlock(Handle handle, long offset, long length) throws IOException {
- if (!isOpen()) {
- throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
- }
- delegate.unlock(handle, offset, length);
- }
-
- @Override
- public int send(int cmd, Buffer buffer) throws IOException {
- if (!isOpen()) {
- throw new IOException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") client is closed");
- }
-
- if (delegate instanceof RawSftpClient) {
- return ((RawSftpClient) delegate).send(cmd, buffer);
- } else {
- throw new StreamCorruptedException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") delegate is not a " + RawSftpClient.class.getSimpleName());
- }
- }
-
- @Override
- public Buffer receive(int id) throws IOException {
- if (!isOpen()) {
- throw new IOException("receive(id=" + id + ") client is closed");
- }
-
- if (delegate instanceof RawSftpClient) {
- return ((RawSftpClient) delegate).receive(id);
- } else {
- throw new StreamCorruptedException("receive(id=" + id + ") delegate is not a " + RawSftpClient.class.getSimpleName());
- }
- }
- }
-
- public static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
- public static final DefaultUserPrincipalLookupService INSTANCE = new DefaultUserPrincipalLookupService();
-
- public DefaultUserPrincipalLookupService() {
- super();
- }
-
- @Override
- public UserPrincipal lookupPrincipalByName(String name) throws IOException {
- return new DefaultUserPrincipal(name);
- }
-
- @Override
- public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
- return new DefaultGroupPrincipal(group);
- }
- }
-
- public static class DefaultUserPrincipal implements UserPrincipal {
-
- private final String name;
-
- public DefaultUserPrincipal(String name) {
- this.name = Objects.requireNonNull(name, "name is null");
- }
-
- @Override
- public final String getName() {
- return name;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- DefaultUserPrincipal that = (DefaultUserPrincipal) o;
- return Objects.equals(this.getName(), that.getName());
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(getName());
- }
-
- @Override
- public String toString() {
- return getName();
- }
- }
-
- public static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
- public DefaultGroupPrincipal(String name) {
- super(name);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
deleted file mode 100644
index 40948bf..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemChannel.java
+++ /dev/null
@@ -1,37 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Objects;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileSystemChannel extends SftpRemotePathChannel {
- public SftpFileSystemChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
- this(Objects.requireNonNull(p, "No target path").toString(), p.getFileSystem(), modes);
- }
-
- public SftpFileSystemChannel(String remotePath, SftpFileSystem fs, Collection<SftpClient.OpenMode> modes) throws IOException {
- super(remotePath, Objects.requireNonNull(fs, "No SFTP file system").getClient(), true, modes);
- }
-}
[5/8] mina-sshd git commit: [SSHD-861] Added
SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
new file mode 100644
index 0000000..1602e9f
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystem.java
@@ -0,0 +1,604 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.nio.charset.Charset;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystemException;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.client.subsystem.sftp.RawSftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpClient;
+import org.apache.sshd.common.file.util.BaseFileSystem;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+
+public class SftpFileSystem extends BaseFileSystem<SftpPath> implements ClientSessionHolder {
+ public static final String POOL_SIZE_PROP = "sftp-fs-pool-size";
+ public static final int DEFAULT_POOL_SIZE = 8;
+
+ public static final NavigableSet<String> UNIVERSAL_SUPPORTED_VIEWS =
+ Collections.unmodifiableNavigableSet(
+ GenericUtils.asSortedSet(String.CASE_INSENSITIVE_ORDER, "basic", "posix", "owner"));
+
+ private final String id;
+ private final ClientSession clientSession;
+ private final SftpClientFactory factory;
+ private final SftpVersionSelector selector;
+ private final Queue<SftpClient> pool;
+ private final ThreadLocal<Wrapper> wrappers = new ThreadLocal<>();
+ private final int version;
+ private final Set<String> supportedViews;
+ private SftpPath defaultDir;
+ private int readBufferSize = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+ private int writeBufferSize = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+ private final List<FileStore> stores;
+
+ public SftpFileSystem(
+ SftpFileSystemProvider provider, String id, ClientSession session,
+ SftpClientFactory factory, SftpVersionSelector selector)
+ throws IOException {
+ super(provider);
+ this.id = id;
+ this.clientSession = Objects.requireNonNull(session, "No client session");
+ this.factory = factory != null ? factory : SftpClientFactory.instance();
+ this.selector = selector;
+ this.stores = Collections.unmodifiableList(Collections.<FileStore>singletonList(new SftpFileStore(id, this)));
+ this.pool = new LinkedBlockingQueue<>(session.getIntProperty(POOL_SIZE_PROP, DEFAULT_POOL_SIZE));
+ try (SftpClient client = getClient()) {
+ version = client.getVersion();
+ defaultDir = getPath(client.canonicalPath("."));
+ }
+
+ if (version >= SftpConstants.SFTP_V4) {
+ Set<String> views = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ views.addAll(UNIVERSAL_SUPPORTED_VIEWS);
+ views.add("acl");
+ supportedViews = Collections.unmodifiableSet(views);
+ } else {
+ supportedViews = UNIVERSAL_SUPPORTED_VIEWS;
+ }
+ }
+
+ public final SftpVersionSelector getSftpVersionSelector() {
+ return selector;
+ }
+
+ public final String getId() {
+ return id;
+ }
+
+ public final int getVersion() {
+ return version;
+ }
+
+ @Override
+ public SftpFileSystemProvider provider() {
+ return (SftpFileSystemProvider) super.provider();
+ }
+
+ @Override // NOTE: co-variant return
+ public List<FileStore> getFileStores() {
+ return this.stores;
+ }
+
+ public int getReadBufferSize() {
+ return readBufferSize;
+ }
+
+ public void setReadBufferSize(int size) {
+ if (size < SftpClient.MIN_READ_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient read buffer size: " + size + ", min.=" + SftpClient.MIN_READ_BUFFER_SIZE);
+ }
+
+ readBufferSize = size;
+ }
+
+ public int getWriteBufferSize() {
+ return writeBufferSize;
+ }
+
+ public void setWriteBufferSize(int size) {
+ if (size < SftpClient.MIN_WRITE_BUFFER_SIZE) {
+ throw new IllegalArgumentException("Insufficient write buffer size: " + size + ", min.=" + SftpClient.MIN_WRITE_BUFFER_SIZE);
+ }
+
+ writeBufferSize = size;
+ }
+
+ @Override
+ protected SftpPath create(String root, List<String> names) {
+ return new SftpPath(this, root, names);
+ }
+
+ @Override
+ public ClientSession getClientSession() {
+ return clientSession;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ public SftpClient getClient() throws IOException {
+ Wrapper wrapper = wrappers.get();
+ if (wrapper == null) {
+ while (wrapper == null) {
+ SftpClient client = pool.poll();
+ if (client == null) {
+ ClientSession session = getClientSession();
+ client = factory.createSftpClient(session, getSftpVersionSelector());
+ }
+ if (!client.isClosing()) {
+ wrapper = new Wrapper(client, getReadBufferSize(), getWriteBufferSize());
+ }
+ }
+ wrappers.set(wrapper);
+ } else {
+ wrapper.increment();
+ }
+ return wrapper;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (isOpen()) {
+ SftpFileSystemProvider provider = provider();
+ String fsId = getId();
+ SftpFileSystem fs = provider.removeFileSystem(fsId);
+ ClientSession session = getClientSession();
+ session.close(true);
+
+ if ((fs != null) && (fs != this)) {
+ throw new FileSystemException(fsId, fsId, "Mismatched FS instance for id=" + fsId);
+ }
+ }
+ }
+
+ @Override
+ public boolean isOpen() {
+ ClientSession session = getClientSession();
+ return session.isOpen();
+ }
+
+ @Override
+ public Set<String> supportedFileAttributeViews() {
+ return supportedViews;
+ }
+
+ @Override
+ public UserPrincipalLookupService getUserPrincipalLookupService() {
+ return DefaultUserPrincipalLookupService.INSTANCE;
+ }
+
+ @Override
+ public SftpPath getDefaultDir() {
+ return defaultDir;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + getClientSession() + "]";
+ }
+
+ private final class Wrapper extends AbstractSftpClient {
+ private final SftpClient delegate;
+ private final AtomicInteger count = new AtomicInteger(1);
+ private final int readSize;
+ private final int writeSize;
+
+ private Wrapper(SftpClient delegate, int readSize, int writeSize) {
+ this.delegate = delegate;
+ this.readSize = readSize;
+ this.writeSize = writeSize;
+ }
+
+ @Override
+ public int getVersion() {
+ return delegate.getVersion();
+ }
+
+ @Override
+ public ClientSession getClientSession() {
+ return delegate.getClientSession();
+ }
+
+ @Override
+ public ClientChannel getClientChannel() {
+ return delegate.getClientChannel();
+ }
+
+ @Override
+ public NavigableMap<String, byte[]> getServerExtensions() {
+ return delegate.getServerExtensions();
+ }
+
+ @Override
+ public Charset getNameDecodingCharset() {
+ return delegate.getNameDecodingCharset();
+ }
+
+ @Override
+ public void setNameDecodingCharset(Charset cs) {
+ delegate.setNameDecodingCharset(cs);
+ }
+
+ @Override
+ public boolean isClosing() {
+ return false;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return count.get() > 0;
+ }
+
+ @SuppressWarnings("synthetic-access")
+ @Override
+ public void close() throws IOException {
+ if (count.decrementAndGet() <= 0) {
+ if (!pool.offer(delegate)) {
+ delegate.close();
+ }
+ wrappers.set(null);
+ }
+ }
+
+ public void increment() {
+ count.incrementAndGet();
+ }
+
+ @Override
+ public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("open(" + path + ")[" + options + "] client is closed");
+ }
+ return delegate.open(path, options);
+ }
+
+ @Override
+ public void close(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("close(" + handle + ") client is closed");
+ }
+ delegate.close(handle);
+ }
+
+ @Override
+ public void remove(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("remove(" + path + ") client is closed");
+ }
+ delegate.remove(path);
+ }
+
+ @Override
+ public void rename(String oldPath, String newPath, Collection<CopyMode> options) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rename(" + oldPath + " => " + newPath + ")[" + options + "] client is closed");
+ }
+ delegate.rename(oldPath, newPath, options);
+ }
+
+ @Override
+ public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed");
+ }
+ return delegate.read(handle, fileOffset, dst, dstOffset, len);
+ }
+
+ @Override
+ public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed");
+ }
+ delegate.write(handle, fileOffset, src, srcOffset, len);
+ }
+
+ @Override
+ public void mkdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("mkdir(" + path + ") client is closed");
+ }
+ delegate.mkdir(path);
+ }
+
+ @Override
+ public void rmdir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("rmdir(" + path + ") client is closed");
+ }
+ delegate.rmdir(path);
+ }
+
+ @Override
+ public CloseableHandle openDir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("openDir(" + path + ") client is closed");
+ }
+ return delegate.openDir(path);
+ }
+
+ @Override
+ public List<DirEntry> readDir(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readDir(" + handle + ") client is closed");
+ }
+ return delegate.readDir(handle);
+ }
+
+ @Override
+ public Iterable<DirEntry> listDir(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readDir(" + handle + ") client is closed");
+ }
+ return delegate.listDir(handle);
+ }
+
+ @Override
+ public String canonicalPath(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("canonicalPath(" + path + ") client is closed");
+ }
+ return delegate.canonicalPath(path);
+ }
+
+ @Override
+ public Attributes stat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + path + ") client is closed");
+ }
+ return delegate.stat(path);
+ }
+
+ @Override
+ public Attributes lstat(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lstat(" + path + ") client is closed");
+ }
+ return delegate.lstat(path);
+ }
+
+ @Override
+ public Attributes stat(Handle handle) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("stat(" + handle + ") client is closed");
+ }
+ return delegate.stat(handle);
+ }
+
+ @Override
+ public void setStat(String path, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed");
+ }
+ delegate.setStat(path, attributes);
+ }
+
+ @Override
+ public void setStat(Handle handle, Attributes attributes) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed");
+ }
+ delegate.setStat(handle, attributes);
+ }
+
+ @Override
+ public String readLink(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readLink(" + path + ") client is closed");
+ }
+ return delegate.readLink(path);
+ }
+
+ @Override
+ public void symLink(String linkPath, String targetPath) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("symLink(" + linkPath + " => " + targetPath + ") client is closed");
+ }
+ delegate.symLink(linkPath, targetPath);
+ }
+
+ @Override
+ public Iterable<DirEntry> readDir(String path) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("readDir(" + path + ") client is closed");
+ }
+ return delegate.readDir(path);
+ }
+
+ @Override
+ public InputStream read(String path) throws IOException {
+ return read(path, readSize);
+ }
+
+ @Override
+ public InputStream read(String path, OpenMode... mode) throws IOException {
+ return read(path, readSize, mode);
+ }
+
+ @Override
+ public InputStream read(String path, Collection<OpenMode> mode) throws IOException {
+ return read(path, readSize, mode);
+ }
+
+ @Override
+ public InputStream read(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("read(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+ }
+ return delegate.read(path, bufferSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path) throws IOException {
+ return write(path, writeSize);
+ }
+
+ @Override
+ public OutputStream write(String path, OpenMode... mode) throws IOException {
+ return write(path, writeSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, Collection<OpenMode> mode) throws IOException {
+ return write(path, writeSize, mode);
+ }
+
+ @Override
+ public OutputStream write(String path, int bufferSize, Collection<OpenMode> mode) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("write(" + path + ")[" + mode + "] size=" + bufferSize + ": client is closed");
+ }
+ return delegate.write(path, bufferSize, mode);
+ }
+
+ @Override
+ public void link(String linkPath, String targetPath, boolean symbolic) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("link(" + linkPath + " => " + targetPath + "] symbolic=" + symbolic + ": client is closed");
+ }
+ delegate.link(linkPath, targetPath, symbolic);
+ }
+
+ @Override
+ public void lock(Handle handle, long offset, long length, int mask) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("lock(" + handle + ")[offset=" + offset + ", length=" + length + ", mask=0x" + Integer.toHexString(mask) + "] client is closed");
+ }
+ delegate.lock(handle, offset, length, mask);
+ }
+
+ @Override
+ public void unlock(Handle handle, long offset, long length) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("unlock" + handle + ")[offset=" + offset + ", length=" + length + "] client is closed");
+ }
+ delegate.unlock(handle, offset, length);
+ }
+
+ @Override
+ public int send(int cmd, Buffer buffer) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") client is closed");
+ }
+
+ if (delegate instanceof RawSftpClient) {
+ return ((RawSftpClient) delegate).send(cmd, buffer);
+ } else {
+ throw new StreamCorruptedException("send(cmd=" + SftpConstants.getCommandMessageName(cmd) + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+ }
+ }
+
+ @Override
+ public Buffer receive(int id) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("receive(id=" + id + ") client is closed");
+ }
+
+ if (delegate instanceof RawSftpClient) {
+ return ((RawSftpClient) delegate).receive(id);
+ } else {
+ throw new StreamCorruptedException("receive(id=" + id + ") delegate is not a " + RawSftpClient.class.getSimpleName());
+ }
+ }
+ }
+
+ public static class DefaultUserPrincipalLookupService extends UserPrincipalLookupService {
+ public static final DefaultUserPrincipalLookupService INSTANCE = new DefaultUserPrincipalLookupService();
+
+ public DefaultUserPrincipalLookupService() {
+ super();
+ }
+
+ @Override
+ public UserPrincipal lookupPrincipalByName(String name) throws IOException {
+ return new DefaultUserPrincipal(name);
+ }
+
+ @Override
+ public GroupPrincipal lookupPrincipalByGroupName(String group) throws IOException {
+ return new DefaultGroupPrincipal(group);
+ }
+ }
+
+ public static class DefaultUserPrincipal implements UserPrincipal {
+
+ private final String name;
+
+ public DefaultUserPrincipal(String name) {
+ this.name = Objects.requireNonNull(name, "name is null");
+ }
+
+ @Override
+ public final String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DefaultUserPrincipal that = (DefaultUserPrincipal) o;
+ return Objects.equals(this.getName(), that.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getName());
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ }
+
+ public static class DefaultGroupPrincipal extends DefaultUserPrincipal implements GroupPrincipal {
+ public DefaultGroupPrincipal(String name) {
+ super(name);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
new file mode 100644
index 0000000..6677a0e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemChannel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpRemotePathChannel;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemChannel extends SftpRemotePathChannel {
+ public SftpFileSystemChannel(SftpPath p, Collection<SftpClient.OpenMode> modes) throws IOException {
+ this(Objects.requireNonNull(p, "No target path").toString(), p.getFileSystem(), modes);
+ }
+
+ public SftpFileSystemChannel(String remotePath, SftpFileSystem fs, Collection<SftpClient.OpenMode> modes) throws IOException {
+ super(remotePath, Objects.requireNonNull(fs, "No SFTP file system").getClient(), true, modes);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
new file mode 100644
index 0000000..576e302
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemClientSessionInitializer.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionCreator;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.auth.PasswordHolder;
+import org.apache.sshd.common.auth.UsernameHolder;
+
+/**
+ * Provides user hooks into the process of creating a {@link SftpFileSystem} via a {@link SftpFileSystemProvider}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface SftpFileSystemClientSessionInitializer {
+ SftpFileSystemClientSessionInitializer DEFAULT = new SftpFileSystemClientSessionInitializer() {
+ @Override
+ public String toString() {
+ return SftpFileSystemClientSessionInitializer.class.getSimpleName() + "[DEFAULT]";
+ }
+ };
+
+ /**
+ * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+ * in order to obtain an initial (non-authenticated) {@link ClientSession}.
+ *
+ * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+ * @param context The initialization {@link SftpFileSystemInitializationContext}
+ * @return The created {@link ClientSession}
+ * @throws IOException If failed to connect
+ */
+ default ClientSession createClientSession(
+ SftpFileSystemProvider provider, SftpFileSystemInitializationContext context)
+ throws IOException {
+ UsernameHolder user = context.getCredentials();
+ ClientSessionCreator client = provider.getClientInstance();
+ return client.connect(user.getUsername(), context.getHost(), context.getPort())
+ .verify(context.getMaxConnectTime())
+ .getSession();
+ }
+
+ /**
+ * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+ * in order to authenticate the session obtained from
+ * {@link #createClientSession(SftpFileSystemProvider, SftpFileSystemInitializationContext)}
+ *
+ * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+ * @param context The initialization {@link SftpFileSystemInitializationContext}
+ * @param session The created {@link ClientSession}
+ * @throws IOException If failed to authenticate
+ */
+ default void authenticateClientSession(
+ SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session)
+ throws IOException {
+ PasswordHolder password = context.getCredentials();
+ session.addPasswordIdentity(password.getPassword());
+ session.auth().verify(context.getMaxAuthTime());
+ }
+
+ /**
+ * Invoked by the {@link SftpFileSystemProvider#newFileSystem(java.net.URI, Map)} method
+ * in order to create the {@link SftpFileSystem} once session has been authenticated.
+ *
+ * @param provider The {@link SftpFileSystemProvider} instance requesting the session
+ * @param context The initialization {@link SftpFileSystemInitializationContext}
+ * @param session The authenticated {@link ClientSession}
+ * @param selector The <U>resolved</U> {@link SftpVersionSelector} to use
+ * @return The created {@link SftpFileSystem}
+ * @throws IOException If failed to create the file-system
+ */
+ default SftpFileSystem createSftpFileSystem(
+ SftpFileSystemProvider provider, SftpFileSystemInitializationContext context, ClientSession session, SftpVersionSelector selector)
+ throws IOException {
+ return new SftpFileSystem(provider, context.getId(), session, provider.getSftpClientFactory(), selector);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
new file mode 100644
index 0000000..b897620
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemInitializationContext.java
@@ -0,0 +1,142 @@
+/*
+ * 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.fs;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemInitializationContext {
+ private final String id;
+ private final URI uri;
+ private final Map<String, ?> environment;
+ private String host;
+ private int port;
+ private BasicCredentialsProvider credentials;
+ private PropertyResolver propertyResolver;
+ private long maxConnectTime;
+ private long maxAuthTime;
+
+ /**
+ * @param id The unique identifier assigned to the file-system being created
+ * @param uri The original {@link URI} that triggered the file-system creation
+ * @param env The environment settings passed along with the URI (may be {@code null})
+ */
+ public SftpFileSystemInitializationContext(String id, URI uri, Map<String, ?> env) {
+ this.id = id;
+ this.uri = uri;
+ this.environment = env;
+ }
+
+ /**
+ * @return The unique identifier assigned to the file-system being created
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @return The original {@link URI} that triggered the file-system creation
+ */
+ public URI getUri() {
+ return uri;
+ }
+
+ /**
+ * @return The environment settings passed along with the URI (may be {@code null})
+ */
+ public Map<String, ?> getEnvironment() {
+ return environment;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ /**
+ * @return The <U>resolved</U> target port from the URI
+ */
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * @return The credentials recovered from the URI
+ */
+ public BasicCredentialsProvider getCredentials() {
+ return credentials;
+ }
+
+ public void setCredentials(BasicCredentialsProvider credentials) {
+ this.credentials = credentials;
+ }
+
+ /**
+ * @return A {@link PropertyResolver} for easy access of any query parameters
+ * encoded in the URI
+ */
+ public PropertyResolver getPropertyResolver() {
+ return propertyResolver;
+ }
+
+ public void setPropertyResolver(PropertyResolver propertyResolver) {
+ this.propertyResolver = propertyResolver;
+ }
+
+ /**
+ * @return The <U>resolved</U> max. connect timeout (msec.)
+ */
+ public long getMaxConnectTime() {
+ return maxConnectTime;
+ }
+
+ public void setMaxConnectTime(long maxConnectTime) {
+ this.maxConnectTime = maxConnectTime;
+ }
+
+ /**
+ * @return The <U>resolved</U> max. authentication timeout (msec.)
+ */
+ public long getMaxAuthTime() {
+ return maxAuthTime;
+ }
+
+ public void setMaxAuthTime(long maxAuthTime) {
+ this.maxAuthTime = maxAuthTime;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + getId() + "]";
+ }
+}
[8/8] mina-sshd git commit: [SSHD-861] Fixed bug in
NumberUtils#isIntegerNumber
Posted by lg...@apache.org.
[SSHD-861] Fixed bug in NumberUtils#isIntegerNumber
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/005ee40c
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/005ee40c
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/005ee40c
Branch: refs/heads/master
Commit: 005ee40c0ac8deb148e7e11fe772d4a449be4c0c
Parents: c64bd7a
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 08:33:56 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200
----------------------------------------------------------------------
.../apache/sshd/common/util/NumberUtils.java | 4 ++--
.../sshd/common/util/NumberUtilsTest.java | 20 ++++++++++++++++++++
2 files changed, 22 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/005ee40c/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
index c8e1817..444113f 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/NumberUtils.java
@@ -290,8 +290,8 @@ public final class NumberUtils {
return false;
}
- for (int index = 0; index < cs.length(); index++) {
- char c = cs.charAt(0);
+ for (int index = 0, len = cs.length(); index < len; index++) {
+ char c = cs.charAt(index);
if ((c >= '0') && (c <= '9')) {
continue;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/005ee40c/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
index 927cecb..d7c2d32 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/NumberUtilsTest.java
@@ -74,4 +74,24 @@ public class NumberUtilsTest extends JUnitTestSupport {
}
}
}
+
+ @Test
+ public void testIsValidIntegerNumber() {
+ for (String s : new String[]{"7", "73", "736", "7365", "19650307"}) {
+ assertTrue(s, NumberUtils.isIntegerNumber(s));
+
+ String pos = "+" + s;
+ assertTrue(pos, NumberUtils.isIntegerNumber(pos));
+
+ String neg = "-" + s;
+ assertTrue(neg, NumberUtils.isIntegerNumber(neg));
+ }
+ }
+
+ @Test
+ public void testIsInvalidIntegerNumber() {
+ for (String s : new String[]{null, "", " ", getCurrentTestName(), "3rd", "3.14", "-.3"}) {
+ assertFalse(s, NumberUtils.isIntegerNumber(s));
+ }
+ }
}
[2/8] mina-sshd git commit: [SSHD-861] Handle correctly special
characters in username/password when creating an SFTP file system URI
Posted by lg...@apache.org.
[SSHD-861] Handle correctly special characters in username/password when creating an SFTP file system URI
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/c24635d5
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/c24635d5
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/c24635d5
Branch: refs/heads/master
Commit: c24635d5cb86a106f5ca33f22e2a1b39d7aad512
Parents: 005ee40
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 11 08:59:46 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 11 18:56:28 2018 +0200
----------------------------------------------------------------------
.../sshd/common/auth/BasicCredentialsImpl.java | 92 ++++++++++++++
.../common/auth/BasicCredentialsProvider.java | 27 +++++
.../common/auth/MutableBasicCredentials.java | 27 +++++
.../sshd/common/auth/MutablePassword.java | 27 +++++
.../apache/sshd/common/auth/PasswordHolder.java | 28 +++++
.../loader/PrivateKeyEncryptionContext.java | 5 +-
.../apache/sshd/util/test/JUnitTestSupport.java | 22 +++-
.../apache/sshd/util/test/SimpleUserInfo.java | 3 +-
.../subsystem/sftp/SftpFileSystemProvider.java | 92 +++++++++++---
.../subsystem/sftp/SftpFileSystemURITest.java | 121 +++++++++++++++++++
.../sftp/ApacheSshdSftpSessionFactory.java | 15 ++-
.../sftp/ApacheSshdSftpSessionFactoryTest.java | 2 +-
12 files changed, 432 insertions(+), 29 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java
new file mode 100644
index 0000000..4e444f0
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.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.common.auth;
+
+import java.util.Objects;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class BasicCredentialsImpl implements MutableBasicCredentials, Cloneable {
+ private String username;
+ private String password;
+
+ public BasicCredentialsImpl() {
+ super();
+ }
+
+ public BasicCredentialsImpl(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ @Override
+ public BasicCredentialsImpl clone() {
+ try {
+ return getClass().cast(super.clone());
+ } catch (CloneNotSupportedException e) {
+ throw new UnsupportedOperationException("Unexpected failure to clone: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getUsername(), getPassword());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ BasicCredentialsImpl other = (BasicCredentialsImpl) obj;
+ return Objects.equals(getUsername(), other.getUsername())
+ && Objects.equals(getPassword(), other.getPassword());
+ }
+
+ // NOTE: do not implement 'toString' on purpose to avoid inadvertent logging of contents
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
new file mode 100644
index 0000000..7ddde04
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface BasicCredentialsProvider extends UsernameHolder, PasswordHolder {
+ // Nothing extra
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
new file mode 100644
index 0000000..a7bb1d5
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java
@@ -0,0 +1,27 @@
+/*
+ * 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.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MutableBasicCredentials extends BasicCredentialsProvider, MutableUserHolder, MutablePassword {
+ // Nothing extra
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
new file mode 100644
index 0000000..07e4843
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java
@@ -0,0 +1,27 @@
+/*
+ * 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.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface MutablePassword extends PasswordHolder {
+ void setPassword(String password);
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
new file mode 100644
index 0000000..5f2eff7
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.auth;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FunctionalInterface
+public interface PasswordHolder {
+ String getPassword();
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
index 5e03cdb..5303d57 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java
@@ -30,13 +30,14 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.sshd.common.auth.MutablePassword;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class PrivateKeyEncryptionContext implements Cloneable {
+public class PrivateKeyEncryptionContext implements MutablePassword, Cloneable {
public static final String DEFAULT_CIPHER_MODE = "CBC";
private static final Map<String, PrivateKeyObfuscator> OBFUSCATORS =
@@ -82,10 +83,12 @@ public class PrivateKeyEncryptionContext implements Cloneable {
cipherMode = value;
}
+ @Override
public String getPassword() {
return password;
}
+ @Override
public void setPassword(String value) {
password = value;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
index 01ba808..175cd93 100644
--- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
+++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java
@@ -51,6 +51,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiPredicate;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.GenericUtils;
@@ -360,6 +361,11 @@ public abstract class JUnitTestSupport extends Assert {
}
public static <E> void assertListEquals(String message, List<? extends E> expected, List<? extends E> actual) {
+ assertListEquals(message, expected, actual, Objects::equals);
+ }
+
+ public static <E> void assertListEquals(
+ String message, List<? extends E> expected, List<? extends E> actual, BiPredicate<? super E, ? super E> equator) {
int expSize = GenericUtils.size(expected);
int actSize = GenericUtils.size(actual);
assertEquals(message + "[size]", expSize, actSize);
@@ -367,18 +373,28 @@ public abstract class JUnitTestSupport extends Assert {
for (int index = 0; index < expSize; index++) {
E expValue = expected.get(index);
E actValue = actual.get(index);
- assertEquals(message + "[" + index + "]", expValue, actValue);
+ if (!equator.test(expValue, actValue)) {
+ fail(message + "[" + index + "]: expected=" + expValue + ", actual=" + actValue);
+ }
}
}
- public static <K, V> void assertMapEquals(String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) {
+ public static <K, V> void assertMapEquals(
+ String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) {
+ assertMapEquals(message, expected, actual, Objects::equals);
+ }
+
+ public static <K, V> void assertMapEquals(
+ String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual, BiPredicate<? super V, ? super V> equator) {
int numItems = GenericUtils.size(expected);
assertEquals(message + "[size]", numItems, GenericUtils.size(actual));
if (numItems > 0) {
expected.forEach((key, expValue) -> {
V actValue = actual.get(key);
- assertEquals(message + "[" + key + "]", expValue, actValue);
+ if (!equator.test(expValue, actValue)) {
+ fail(message + "[" + key + "]: expected=" + expValue + ", actual=" + actValue);
+ }
});
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
index 31f7c41..5062e8d 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java
@@ -65,7 +65,8 @@ public class SimpleUserInfo implements UserInfo, UIKeyboardInteractive {
}
@Override
- public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) {
+ public String[] promptKeyboardInteractive(
+ String destination, String name, String instruction, String[] prompt, boolean[] echo) {
return new String[]{password};
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
index 3c171d0..f8cd75b 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
@@ -24,6 +24,7 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
@@ -75,6 +76,9 @@ import org.apache.sshd.common.PropertyResolver;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.auth.BasicCredentialsImpl;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.subsystem.sftp.SftpException;
@@ -184,12 +188,9 @@ public class SftpFileSystemProvider extends FileSystemProvider {
port = SshConstants.DEFAULT_PORT;
}
- String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
- String[] ui = GenericUtils.split(userInfo, ':');
- ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
-
- String username = ui[0];
- String password = ui[1];
+ BasicCredentialsProvider credentials = parseCredentials(uri);
+ ValidateUtils.checkState(credentials != null, "No credentials provided");
+ String username = credentials.getUsername();
String id = getFileSystemIdentifier(host, port, username);
Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
@@ -198,6 +199,7 @@ public class SftpFileSystemProvider extends FileSystemProvider {
PropertyResolverUtils.getCharset(resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME);
long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME);
+ String password = credentials.getPassword();
SftpFileSystem fileSystem;
synchronized (fileSystems) {
@@ -308,6 +310,27 @@ public class SftpFileSystemProvider extends FileSystemProvider {
return resolved;
}
+ /**
+ * Attempts to parse the user information from the URI
+ *
+ * @param uri The {@link URI} value - ignored if {@code null} or does not
+ * contain any {@link URI#getUserInfo() user info}.
+ * @return The parsed credentials - {@code null} if none available
+ */
+ public static MutableBasicCredentials parseCredentials(URI uri) {
+ return parseCredentials((uri == null) ? "" : uri.getUserInfo());
+ }
+
+ public static MutableBasicCredentials parseCredentials(String userInfo) {
+ if (GenericUtils.isEmpty(userInfo)) {
+ return null;
+ }
+
+ String[] ui = GenericUtils.split(userInfo, ':');
+ ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+ return new BasicCredentialsImpl(ui[0], ui[1]);
+ }
+
public static Map<String, Object> parseURIParameters(URI uri) {
return parseURIParameters((uri == null) ? "" : uri.getQuery());
}
@@ -336,7 +359,9 @@ public class SftpFileSystemProvider extends FileSystemProvider {
String key = p.substring(0, pos);
String value = p.substring(pos + 1);
if (NumberUtils.isIntegerNumber(value)) {
- map.put(key, Long.parseLong(value));
+ map.put(key, Long.valueOf(value));
+ } else if ("true".equals(value) || "false".equals("value")) {
+ map.put(key, Boolean.valueOf(value));
} else {
map.put(key, value);
}
@@ -1235,22 +1260,53 @@ public class SftpFileSystemProvider extends FileSystemProvider {
}
public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
- StringBuilder sb = new StringBuilder(Byte.MAX_VALUE);
- sb.append(SftpConstants.SFTP_SUBSYSTEM_NAME)
- .append("://").append(username).append(':').append(password)
- .append('@').append(host).append(':').append(port)
- .append('/');
- if (GenericUtils.size(params) > 0) {
- boolean firstParam = true;
- // Cannot use forEach because firstParam is not effectively final
+ ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
+
+ String queryPart = null;
+ int numParams = GenericUtils.size(params);
+ if (numParams > 0) {
+ StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
for (Map.Entry<String, ?> pe : params.entrySet()) {
String key = pe.getKey();
Object value = pe.getValue();
- sb.append(firstParam ? '?' : '&').append(key).append('=').append(Objects.toString(value, null));
- firstParam = false;
+ if (sb.length() > 0) {
+ sb.append('&');
+ }
+ sb.append(key);
+ if (value != null) {
+ sb.append('=').append(Objects.toString(value, null));
+ }
}
+
+ queryPart = sb.toString();
}
- return URI.create(sb.toString());
+ try {
+ String userAuth = encodeCredentials(username, password);
+ return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
+ + " to create access URI: " + e.getMessage(), e);
+ }
+ }
+
+ public static String encodeCredentials(String username, String password) {
+ ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
+ ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+ /*
+ * There is no way to properly encode/decode credentials that already contain
+ * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
+ *
+ *
+ * Use of the format "user:password" in the userinfo field is
+ * deprecated. Applications should not render as clear text any data
+ * after the first colon (":") character found within a userinfo
+ * subcomponent unless the data after the colon is the empty string
+ * (indicating no password). Applications may choose to ignore or
+ * reject such data when it is received as part of a reference and
+ * should reject the storage of such data in unencrypted form.
+ */
+ ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
+ return username + ":" + password;
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
new file mode 100644
index 0000000..1e89e47
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+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;
+
+/**
+ * TODO Add javadoc
+ *
+ * @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)
+@Category({ NoIoTestCase.class })
+public class SftpFileSystemURITest extends JUnitTestSupport {
+ private final String host;
+ private final int port;
+ private final String username;
+ private final String password;
+ private final Map<String, ?> params;
+
+ public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.params = params;
+ }
+
+ @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
+ public static List<Object[]> parameters() {
+ return new ArrayList<Object[]>() {
+ // Not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
+ add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
+ add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
+ // not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ put("param1", "1st");
+ put("param2", 2);
+ put("param3", false);
+ }
+ }
+ });
+ add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+ }
+ };
+ }
+
+ @Test
+ public void testFullURIEncoding() {
+ URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
+ assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
+ assertEquals("Mismatched host", host, uri.getHost());
+ assertEquals("Mismatched port", port, uri.getPort());
+
+ BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
+ assertNotNull("No credentials provided", credentials);
+ assertEquals("Mismatched user", username, credentials.getUsername());
+ assertEquals("Mismatched password", password, credentials.getPassword());
+
+ Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
+ assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + "[host=" + host
+ + ", port=" + port
+ + ", username=" + username
+ + ", password=" + password
+ + ", params=" + params
+ + "]";
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
----------------------------------------------------------------------
diff --git a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
index dfa5855..3c0a65c 100644
--- a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
+++ b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java
@@ -38,6 +38,7 @@ import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils;
@@ -62,7 +63,7 @@ import org.springframework.integration.file.remote.session.SharedSessionCapable;
public class ApacheSshdSftpSessionFactory
extends AbstractLoggingBean
implements SessionFactory<DirEntry>, SharedSessionCapable,
- SimpleClientConfigurator,
+ MutableBasicCredentials, SimpleClientConfigurator,
InitializingBean, DisposableBean {
// TODO add support for loading multiple private keys
@@ -119,19 +120,22 @@ public class ApacheSshdSftpSessionFactory
this.portValue = port;
}
- public String getUser() {
+ @Override
+ public String getUsername() {
return userValue;
}
/**
* The remote user to use. This is a mandatory property.
*
- * @param user The username
+ * @param user The (never {@code null}/empty) username
*/
- public void setUser(String user) {
+ @Override
+ public void setUsername(String user) {
this.userValue = ValidateUtils.checkNotNullAndNotEmpty(user, "No user specified: %s", user);
}
+ @Override
public String getPassword() {
return passwordValue;
}
@@ -143,6 +147,7 @@ public class ApacheSshdSftpSessionFactory
* @param password The password to use - if {@code null} then no password
* is set - in which case the {@link #getPrivateKey()} resource is used
*/
+ @Override
public void setPassword(String password) {
this.passwordValue = password;
}
@@ -396,7 +401,7 @@ public class ApacheSshdSftpSessionFactory
protected ClientSession createClientSession() throws Exception {
String hostname = ValidateUtils.checkNotNullAndNotEmpty(getHost(), "Host must not be empty");
- String username = ValidateUtils.checkNotNullAndNotEmpty(getUser(), "User must not be empty");
+ String username = ValidateUtils.checkNotNullAndNotEmpty(getUsername(), "User must not be empty");
String passwordIdentity = getPassword();
KeyPair kp = getPrivateKeyPair();
ValidateUtils.checkState(GenericUtils.isNotEmpty(passwordIdentity) || (kp != null),
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
----------------------------------------------------------------------
diff --git a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
index 75d7195..4b85c20 100644
--- a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
+++ b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java
@@ -388,7 +388,7 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport {
ApacheSshdSftpSessionFactory factory = new ApacheSshdSftpSessionFactory(sharedSession);
factory.setHost(TEST_LOCALHOST);
factory.setPort(port);
- factory.setUser(getCurrentTestName());
+ factory.setUsername(getCurrentTestName());
factory.setPassword(getCurrentTestName());
factory.setSshClient(client);
factory.setConnectTimeout(TimeUnit.SECONDS.toMillis(7L));
[3/8] mina-sshd git commit: [SSHD-861] Added
SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
deleted file mode 100644
index 9ecb690..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemTest.java
+++ /dev/null
@@ -1,497 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-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.StandardOpenOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.FileAttributeView;
-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.nio.file.spi.FileSystemProvider;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
-import org.apache.sshd.common.session.Session;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.OsUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.apache.sshd.server.SshServer;
-import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
-import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
-import org.apache.sshd.util.test.BaseTestSupport;
-import org.apache.sshd.util.test.CommonTestSupportUtils;
-import org.apache.sshd.util.test.CoreTestSupportUtils;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.runners.MethodSorters;
-
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@SuppressWarnings("checkstyle:MethodCount")
-public class SftpFileSystemTest extends BaseTestSupport {
- private static SshServer sshd;
- private static int port;
-
- private final FileSystemFactory fileSystemFactory;
-
- public SftpFileSystemTest() throws IOException {
- Path targetPath = detectTargetFolder();
- Path parentPath = targetPath.getParent();
- fileSystemFactory = new VirtualFileSystemFactory(parentPath);
- }
-
- @BeforeClass
- public static void setupServerInstance() throws Exception {
- sshd = CoreTestSupportUtils.setupTestServer(SftpFileSystemTest.class);
- sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
- sshd.start();
- port = sshd.getPort();
- }
-
- @AfterClass
- public static void tearDownServerInstance() throws Exception {
- if (sshd != null) {
- try {
- sshd.stop(true);
- } finally {
- sshd = null;
- }
- }
- }
-
- @Before
- public void setUp() throws Exception {
- sshd.setFileSystemFactory(fileSystemFactory);
- }
-
- @Test
- public void testFileSystem() throws Exception {
- try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
- GenericUtils.<String, Object>mapBuilder()
- .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
- .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
- .build())) {
- assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
- testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
- }
- }
-
- @Test // see SSHD-578
- public void testFileSystemURIParameters() throws Exception {
- Map<String, Object> params = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- params.put("test-class-name", getClass().getSimpleName());
- params.put("test-pkg-name", getClass().getPackage().getName());
- params.put("test-name", getCurrentTestName());
-
- int expectedVersion = (SftpSubsystemEnvironment.LOWER_SFTP_IMPL + SftpSubsystemEnvironment.HIGHER_SFTP_IMPL) / 2;
- params.put(SftpFileSystemProvider.VERSION_PARAM, expectedVersion);
- try (SftpFileSystem fs = (SftpFileSystem) FileSystems.newFileSystem(createDefaultFileSystemURI(params), Collections.<String, Object>emptyMap())) {
- try (SftpClient sftpClient = fs.getClient()) {
- assertEquals("Mismatched negotiated version", expectedVersion, sftpClient.getVersion());
-
- Session session = sftpClient.getClientSession();
- params.forEach((key, expected) -> {
- if (SftpFileSystemProvider.VERSION_PARAM.equalsIgnoreCase(key)) {
- return;
- }
-
- Object actual = session.getObject(key);
- assertEquals("Mismatched value for param '" + key + "'", expected, actual);
- });
- }
- }
- }
-
- @Test
- public void testAttributes() throws IOException {
- Path targetPath = detectTargetFolder();
- Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
- SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- CommonTestSupportUtils.deleteRecursive(lclSftp);
-
- try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
- GenericUtils.<String, Object>mapBuilder()
- .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, SftpClient.MIN_READ_BUFFER_SIZE)
- .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, SftpClient.MIN_WRITE_BUFFER_SIZE)
- .build())) {
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
- Path file = fs.getPath(remFilePath);
- assertHierarchyTargetFolderExists(file.getParent());
- Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
-
- Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
- assertNotNull("No attributes read for " + file, attrs);
-
- Files.setAttribute(file, "basic:size", 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();
- Path rootNative = targetPath.resolve("root").toAbsolutePath();
- CommonTestSupportUtils.deleteRecursive(rootNative);
- assertHierarchyTargetFolderExists(rootNative);
-
- try (FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
- Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
- outputDebugMessage("Created %s", dir);
- }
- }
-
- @Test // see SSHD-697
- public void testFileChannel() throws IOException {
- Path targetPath = detectTargetFolder();
- Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
- SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
- Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
- Files.deleteIfExists(lclFile);
- byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
- try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
- Path parentPath = targetPath.getParent();
- String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
- Path file = fs.getPath(remFilePath);
-
- FileSystemProvider provider = fs.provider();
- try (FileChannel fc = provider.newFileChannel(file, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
- int writeLen = fc.write(ByteBuffer.wrap(expected));
- assertEquals("Mismatched written length", expected.length, writeLen);
-
- FileChannel fcPos = fc.position(0L);
- assertSame("Mismatched positioned file channel", fc, fcPos);
-
- byte[] actual = new byte[expected.length];
- int readLen = fc.read(ByteBuffer.wrap(actual));
- assertEquals("Mismatched read len", writeLen, readLen);
- assertArrayEquals("Mismatched read data", expected, actual);
- }
- }
-
- byte[] actual = Files.readAllBytes(lclFile);
- assertArrayEquals("Mismatched persisted data", expected, actual);
- }
-
- @Test
- public void testFileStore() throws IOException {
- try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
- Iterable<FileStore> iter = fs.getFileStores();
- assertTrue("Not a list", iter instanceof List<?>);
-
- List<FileStore> list = (List<FileStore>) iter;
- assertEquals("Mismatched stores count", 1, list.size());
-
- FileStore store = list.get(0);
- assertEquals("Mismatched type", SftpConstants.SFTP_SUBSYSTEM_NAME, store.type());
- assertFalse("Read-only ?", store.isReadOnly());
-
- for (String name : fs.supportedFileAttributeViews()) {
- assertTrue("Unsupported view name: " + name, store.supportsFileAttributeView(name));
- }
-
- for (Class<? extends FileAttributeView> type : SftpFileSystemProvider.UNIVERSAL_SUPPORTED_VIEWS) {
- assertTrue("Unsupported view type: " + type.getSimpleName(), store.supportsFileAttributeView(type));
- }
- }
- }
-
- @Test
- public void testMultipleFileStoresOnSameProvider() throws IOException {
- try (SshClient client = setupTestClient()) {
- client.start();
-
- SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
- Collection<SftpFileSystem> fsList = new LinkedList<>();
- try {
- Collection<String> idSet = new HashSet<>();
- Map<String, Object> empty = Collections.emptyMap();
- for (int index = 0; index < 4; index++) {
- String credentials = getCurrentTestName() + "-user-" + index;
- SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
- fsList.add(expected);
-
- String id = expected.getId();
- assertTrue("Non unique file system id: " + id, idSet.add(id));
-
- SftpFileSystem actual = provider.getFileSystem(id);
- assertSame("Mismatched cached instances for " + id, expected, actual);
- outputDebugMessage("Created file system id: %s", id);
- }
-
- for (SftpFileSystem fs : fsList) {
- String id = fs.getId();
- fs.close();
- assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
- }
- } finally {
- IOException err = null;
- for (FileSystem fs : fsList) {
- try {
- fs.close();
- } catch (IOException e) {
- err = GenericUtils.accumulateException(err, e);
- }
- }
-
- client.stop();
-
- if (err != null) {
- throw err;
- }
- }
- }
- }
-
- @Test
- public void testSftpVersionSelector() throws Exception {
- final AtomicInteger selected = new AtomicInteger(-1);
- SftpVersionSelector selector = (session, current, available) -> {
- int value = GenericUtils.stream(available)
- .mapToInt(Integer::intValue)
- .filter(v -> v != current)
- .max()
- .orElseGet(() -> current);
- selected.set(value);
- return value;
- };
-
- 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 (FileSystem fs = createSftpFileSystem(session, selector)) {
- assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
- Collection<String> views = fs.supportedFileAttributeViews();
- assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
- views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
- int expectedVersion = selected.get();
- assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
- testFileSystem(fs, expectedVersion);
- }
- } finally {
- client.stop();
- }
- }
- }
-
- private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
- return SftpClientFactory.instance().createSftpFileSystem(session, selector);
- }
-
- private void testFileSystem(FileSystem fs, int version) throws Exception {
- Iterable<Path> rootDirs = fs.getRootDirectories();
- for (Path root : rootDirs) {
- String rootName = root.toString();
- try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
- for (Path child : ds) {
- String name = child.getFileName().toString();
- assertNotEquals("Unexpected dot name", ".", name);
- assertNotEquals("Unexpected dotdot name", "..", name);
- outputDebugMessage("[%s] %s", rootName, 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 targetPath = detectTargetFolder();
- Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
- SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
- CommonTestSupportUtils.deleteRecursive(lclSftp);
-
- Path current = fs.getPath(".").toRealPath().normalize();
- outputDebugMessage("CWD: %s", current);
-
- Path parentPath = targetPath.getParent();
- Path clientFolder = lclSftp.resolve("client");
- String remFile1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
- Path file1 = fs.getPath(remFile1Path);
- assertHierarchyTargetFolderExists(file1.getParent());
-
- String expected = "Hello world: " + getCurrentTestName();
- outputDebugMessage("Write initial data to %s", file1);
- Files.write(file1, expected.getBytes(StandardCharsets.UTF_8));
- String buf = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
- assertEquals("Mismatched read test data", expected, buf);
-
- if (version >= SftpConstants.SFTP_V4) {
- outputDebugMessage("getFileAttributeView(%s)", file1);
- AclFileAttributeView aclView = Files.getFileAttributeView(file1, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
- assertNotNull("No ACL view for " + file1, aclView);
-
- Map<String, ?> attrs = Files.readAttributes(file1, "acl:*", LinkOption.NOFOLLOW_LINKS);
- outputDebugMessage("readAttributes(%s) %s", file1, attrs);
- assertEquals("Mismatched owner for " + file1, aclView.getOwner(), attrs.get("owner"));
-
- @SuppressWarnings("unchecked")
- List<AclEntry> acl = (List<AclEntry>) attrs.get("acl");
- outputDebugMessage("acls(%s) %s", file1, acl);
- assertListEquals("Mismatched ACLs for " + file1, aclView.getAcl(), acl);
- }
-
- String remFile2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
- Path file2 = fs.getPath(remFile2Path);
- String remFile3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
- Path file3 = fs.getPath(remFile3Path);
- try {
- outputDebugMessage("Move with failure expected %s => %s", file2, file3);
- Files.move(file2, file3, LinkOption.NOFOLLOW_LINKS);
- fail("Unexpected success in moving " + file2 + " => " + file3);
- } catch (NoSuchFileException e) {
- // expected
- }
-
- Files.write(file2, "h".getBytes(StandardCharsets.UTF_8));
- try {
- outputDebugMessage("Move with failure expected %s => %s", file1, file2);
- Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS);
- fail("Unexpected success in moving " + file1 + " => " + file2);
- } catch (FileAlreadyExistsException e) {
- // expected
- }
-
- outputDebugMessage("Move with success expected %s => %s", file1, file2);
- Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
- outputDebugMessage("Move with success expected %s => %s", file2, file1);
- Files.move(file2, file1, LinkOption.NOFOLLOW_LINKS);
-
- Map<String, Object> attrs = Files.readAttributes(file1, "*");
- outputDebugMessage("%s attributes: %s", file1, attrs);
-
- // TODO there are many issues with symbolic links on Windows
- if (OsUtils.isUNIX()) {
- Path link = fs.getPath(remFile2Path);
- Path linkParent = link.getParent();
- Path relPath = linkParent.relativize(file1);
- outputDebugMessage("Create symlink %s => %s", link, relPath);
- 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());
-
- outputDebugMessage("Delete symlink %s", link);
- Files.delete(link);
- }
-
- attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
- outputDebugMessage("%s no-follow attributes: %s", file1, attrs);
- assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1), StandardCharsets.UTF_8));
-
- try (FileChannel channel = FileChannel.open(file1)) {
- try (FileLock lock = channel.lock()) {
- outputDebugMessage("Lock %s: %s", file1, lock);
-
- try (FileChannel channel2 = FileChannel.open(file1)) {
- try (FileLock lock2 = channel2.lock()) {
- fail("Unexpected success in re-locking " + file1 + ": " + lock2);
- } catch (OverlappingFileLockException e) {
- // expected
- }
- }
- }
- }
-
- Files.delete(file1);
- }
-
- private URI createDefaultFileSystemURI() {
- return createDefaultFileSystemURI(Collections.emptyMap());
- }
-
- private URI createDefaultFileSystemURI(Map<String, ?> params) {
- return createFileSystemURI(getCurrentTestName(), params);
- }
-
- private URI createFileSystemURI(String username, Map<String, ?> params) {
- return createFileSystemURI(username, port, params);
- }
-
- private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
- return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
deleted file mode 100644
index 1e89e47..0000000
--- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java
+++ /dev/null
@@ -1,121 +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.subsystem.sftp;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.auth.BasicCredentialsProvider;
-import org.apache.sshd.common.subsystem.sftp.SftpConstants;
-import org.apache.sshd.common.util.net.SshdSocketAddress;
-import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
-import org.apache.sshd.util.test.JUnitTestSupport;
-import org.apache.sshd.util.test.NoIoTestCase;
-import org.junit.FixMethodOrder;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-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;
-
-/**
- * TODO Add javadoc
- *
- * @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)
-@Category({ NoIoTestCase.class })
-public class SftpFileSystemURITest extends JUnitTestSupport {
- private final String host;
- private final int port;
- private final String username;
- private final String password;
- private final Map<String, ?> params;
-
- public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- this.params = params;
- }
-
- @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
- public static List<Object[]> parameters() {
- return new ArrayList<Object[]>() {
- // Not serializing it
- private static final long serialVersionUID = 1L;
-
- {
- add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
- add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
- add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
- // not serializing it
- private static final long serialVersionUID = 1L;
-
- {
- put("param1", "1st");
- put("param2", 2);
- put("param3", false);
- }
- }
- });
- add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
- }
- };
- }
-
- @Test
- public void testFullURIEncoding() {
- URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
- assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
- assertEquals("Mismatched host", host, uri.getHost());
- assertEquals("Mismatched port", port, uri.getPort());
-
- BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
- assertNotNull("No credentials provided", credentials);
- assertEquals("Mismatched user", username, credentials.getUsername());
- assertEquals("Mismatched password", password, credentials.getPassword());
-
- Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
- assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName()
- + "[host=" + host
- + ", port=" + port
- + ", username=" + username
- + ", password=" + password
- + ", params=" + params
- + "]";
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
new file mode 100644
index 0000000..5b78325
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemTest.java
@@ -0,0 +1,532 @@
+/*
+ * 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.fs;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+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.StandardOpenOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+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.nio.file.spi.FileSystemProvider;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.file.FileSystemFactory;
+import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemEnvironment;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.CommonTestSupportUtils;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@SuppressWarnings("checkstyle:MethodCount")
+public class SftpFileSystemTest extends BaseTestSupport {
+ private static SshServer sshd;
+ private static int port;
+
+ private final FileSystemFactory fileSystemFactory;
+
+ public SftpFileSystemTest() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path parentPath = targetPath.getParent();
+ fileSystemFactory = new VirtualFileSystemFactory(parentPath);
+ }
+
+ @BeforeClass
+ public static void setupServerInstance() throws Exception {
+ sshd = CoreTestSupportUtils.setupTestServer(SftpFileSystemTest.class);
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+ sshd.start();
+ port = sshd.getPort();
+ }
+
+ @AfterClass
+ public static void tearDownServerInstance() throws Exception {
+ if (sshd != null) {
+ try {
+ sshd.stop(true);
+ } finally {
+ sshd = null;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ sshd.setFileSystemFactory(fileSystemFactory);
+ }
+
+ @Test
+ public void testFileSystem() throws Exception {
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+ GenericUtils.<String, Object>mapBuilder()
+ .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+ .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, IoUtils.DEFAULT_COPY_SIZE)
+ .build())) {
+ assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+ testFileSystem(fs, ((SftpFileSystem) fs).getVersion());
+ }
+ }
+
+ @Test // see SSHD-578
+ public void testFileSystemURIParameters() throws Exception {
+ Map<String, Object> params = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ params.put("test-class-name", getClass().getSimpleName());
+ params.put("test-pkg-name", getClass().getPackage().getName());
+ params.put("test-name", getCurrentTestName());
+
+ int expectedVersion = (SftpSubsystemEnvironment.LOWER_SFTP_IMPL + SftpSubsystemEnvironment.HIGHER_SFTP_IMPL) / 2;
+ params.put(SftpFileSystemProvider.VERSION_PARAM, expectedVersion);
+ try (SftpFileSystem fs = (SftpFileSystem) FileSystems.newFileSystem(createDefaultFileSystemURI(params), Collections.<String, Object>emptyMap())) {
+ try (SftpClient sftpClient = fs.getClient()) {
+ assertEquals("Mismatched negotiated version", expectedVersion, sftpClient.getVersion());
+
+ Session session = sftpClient.getClientSession();
+ params.forEach((key, expected) -> {
+ if (SftpFileSystemProvider.VERSION_PARAM.equalsIgnoreCase(key)) {
+ return;
+ }
+
+ Object actual = session.getObject(key);
+ assertEquals("Mismatched value for param '" + key + "'", expected, actual);
+ });
+ }
+ }
+ }
+
+ @Test
+ public void testAttributes() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+ SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ CommonTestSupportUtils.deleteRecursive(lclSftp);
+
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(),
+ GenericUtils.<String, Object>mapBuilder()
+ .put(SftpFileSystemProvider.READ_BUFFER_PROP_NAME, SftpClient.MIN_READ_BUFFER_SIZE)
+ .put(SftpFileSystemProvider.WRITE_BUFFER_PROP_NAME, SftpClient.MIN_WRITE_BUFFER_SIZE)
+ .build())) {
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file.txt"));
+ Path file = fs.getPath(remFilePath);
+ assertHierarchyTargetFolderExists(file.getParent());
+ Files.write(file, (getCurrentTestName() + "\n").getBytes(StandardCharsets.UTF_8));
+
+ Map<String, Object> attrs = Files.readAttributes(file, "posix:*");
+ assertNotNull("No attributes read for " + file, attrs);
+
+ Files.setAttribute(file, "basic:size", 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();
+ Path rootNative = targetPath.resolve("root").toAbsolutePath();
+ CommonTestSupportUtils.deleteRecursive(rootNative);
+ assertHierarchyTargetFolderExists(rootNative);
+
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("root:" + rootNative.toUri().toString() + "!/"), null)) {
+ Path dir = assertHierarchyTargetFolderExists(fs.getPath("test/foo"));
+ outputDebugMessage("Created %s", dir);
+ }
+ }
+
+ @Test // see SSHD-697
+ public void testFileChannel() throws IOException {
+ Path targetPath = detectTargetFolder();
+ Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+ SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName());
+ Path lclFile = lclSftp.resolve(getCurrentTestName() + ".txt");
+ Files.deleteIfExists(lclFile);
+ byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8);
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+ Path parentPath = targetPath.getParent();
+ String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile);
+ Path file = fs.getPath(remFilePath);
+
+ FileSystemProvider provider = fs.provider();
+ try (FileChannel fc = provider.newFileChannel(file, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE))) {
+ int writeLen = fc.write(ByteBuffer.wrap(expected));
+ assertEquals("Mismatched written length", expected.length, writeLen);
+
+ FileChannel fcPos = fc.position(0L);
+ assertSame("Mismatched positioned file channel", fc, fcPos);
+
+ byte[] actual = new byte[expected.length];
+ int readLen = fc.read(ByteBuffer.wrap(actual));
+ assertEquals("Mismatched read len", writeLen, readLen);
+ assertArrayEquals("Mismatched read data", expected, actual);
+ }
+ }
+
+ byte[] actual = Files.readAllBytes(lclFile);
+ assertArrayEquals("Mismatched persisted data", expected, actual);
+ }
+
+ @Test
+ public void testFileStore() throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(createDefaultFileSystemURI(), Collections.emptyMap())) {
+ Iterable<FileStore> iter = fs.getFileStores();
+ assertTrue("Not a list", iter instanceof List<?>);
+
+ List<FileStore> list = (List<FileStore>) iter;
+ assertEquals("Mismatched stores count", 1, list.size());
+
+ FileStore store = list.get(0);
+ assertEquals("Mismatched type", SftpConstants.SFTP_SUBSYSTEM_NAME, store.type());
+ assertFalse("Read-only ?", store.isReadOnly());
+
+ for (String name : fs.supportedFileAttributeViews()) {
+ assertTrue("Unsupported view name: " + name, store.supportsFileAttributeView(name));
+ }
+
+ for (Class<? extends FileAttributeView> type : SftpFileSystemProvider.UNIVERSAL_SUPPORTED_VIEWS) {
+ assertTrue("Unsupported view type: " + type.getSimpleName(), store.supportsFileAttributeView(type));
+ }
+ }
+ }
+
+ @Test
+ public void testMultipleFileStoresOnSameProvider() throws IOException {
+ try (SshClient client = setupTestClient()) {
+ client.start();
+
+ SftpFileSystemProvider provider = new SftpFileSystemProvider(client);
+ Collection<SftpFileSystem> fsList = new LinkedList<>();
+ try {
+ Collection<String> idSet = new HashSet<>();
+ Map<String, Object> empty = Collections.emptyMap();
+ for (int index = 0; index < 4; index++) {
+ String credentials = getCurrentTestName() + "-user-" + index;
+ SftpFileSystem expected = provider.newFileSystem(createFileSystemURI(credentials, empty), empty);
+ fsList.add(expected);
+
+ String id = expected.getId();
+ assertTrue("Non unique file system id: " + id, idSet.add(id));
+
+ SftpFileSystem actual = provider.getFileSystem(id);
+ assertSame("Mismatched cached instances for " + id, expected, actual);
+ outputDebugMessage("Created file system id: %s", id);
+ }
+
+ for (SftpFileSystem fs : fsList) {
+ String id = fs.getId();
+ fs.close();
+ assertNull("File system not removed from cache: " + id, provider.getFileSystem(id));
+ }
+ } finally {
+ IOException err = null;
+ for (FileSystem fs : fsList) {
+ try {
+ fs.close();
+ } catch (IOException e) {
+ err = GenericUtils.accumulateException(err, e);
+ }
+ }
+
+ client.stop();
+
+ if (err != null) {
+ throw err;
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testSftpVersionSelector() throws Exception {
+ AtomicInteger selected = new AtomicInteger(-1);
+ SftpVersionSelector selector = (session, current, available) -> {
+ int value = GenericUtils.stream(available)
+ .mapToInt(Integer::intValue)
+ .filter(v -> v != current)
+ .max()
+ .orElseGet(() -> current);
+ selected.set(value);
+ return value;
+ };
+
+ 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 (FileSystem fs = createSftpFileSystem(session, selector)) {
+ assertTrue("Not an SftpFileSystem", fs instanceof SftpFileSystem);
+ Collection<String> views = fs.supportedFileAttributeViews();
+ assertTrue("Universal views (" + SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS + ") not supported: " + views,
+ views.containsAll(SftpFileSystem.UNIVERSAL_SUPPORTED_VIEWS));
+ int expectedVersion = selected.get();
+ assertEquals("Mismatched negotiated version", expectedVersion, ((SftpFileSystem) fs).getVersion());
+ testFileSystem(fs, expectedVersion);
+ }
+ } finally {
+ client.stop();
+ }
+ }
+ }
+
+ @Test
+ public void testFileSystemProviderServiceEntry() throws IOException {
+ Path configFile = CommonTestSupportUtils.resolve(detectSourcesFolder(),
+ MAIN_SUBFOLDER, "filtered-resources", "META-INF", "services", FileSystemProvider.class.getName());
+ assertTrue("Missing " + configFile, Files.exists(configFile));
+
+ boolean found = false;
+ try (InputStream stream = Files.newInputStream(configFile);
+ Reader r = new InputStreamReader(stream, StandardCharsets.UTF_8);
+ BufferedReader b = new BufferedReader(r)) {
+
+ for (String line = b.readLine(); line != null; line = b.readLine()) {
+ line = line.trim();
+ if (GenericUtils.isEmpty(line) || (line.charAt(0) == '#')) {
+ continue;
+ }
+
+ assertFalse("Multiple configurations: " + line, found);
+ assertEquals("Mismatched configuration", SftpFileSystemProvider.class.getName(), line);
+ found = true;
+ }
+ }
+
+ assertTrue("No configuration found", found);
+ }
+
+ private FileSystem createSftpFileSystem(ClientSession session, SftpVersionSelector selector) throws IOException {
+ return SftpClientFactory.instance().createSftpFileSystem(session, selector);
+ }
+
+ private void testFileSystem(FileSystem fs, int version) throws Exception {
+ Iterable<Path> rootDirs = fs.getRootDirectories();
+ for (Path root : rootDirs) {
+ String rootName = root.toString();
+ try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+ for (Path child : ds) {
+ String name = child.getFileName().toString();
+ assertNotEquals("Unexpected dot name", ".", name);
+ assertNotEquals("Unexpected dotdot name", "..", name);
+ outputDebugMessage("[%s] %s", rootName, 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 targetPath = detectTargetFolder();
+ Path lclSftp = CommonTestSupportUtils.resolve(targetPath,
+ SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName());
+ CommonTestSupportUtils.deleteRecursive(lclSftp);
+
+ Path current = fs.getPath(".").toRealPath().normalize();
+ outputDebugMessage("CWD: %s", current);
+
+ Path parentPath = targetPath.getParent();
+ Path clientFolder = lclSftp.resolve("client");
+ String remFile1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-1.txt"));
+ Path file1 = fs.getPath(remFile1Path);
+ assertHierarchyTargetFolderExists(file1.getParent());
+
+ String expected = "Hello world: " + getCurrentTestName();
+ outputDebugMessage("Write initial data to %s", file1);
+ Files.write(file1, expected.getBytes(StandardCharsets.UTF_8));
+ String buf = new String(Files.readAllBytes(file1), StandardCharsets.UTF_8);
+ assertEquals("Mismatched read test data", expected, buf);
+
+ if (version >= SftpConstants.SFTP_V4) {
+ outputDebugMessage("getFileAttributeView(%s)", file1);
+ AclFileAttributeView aclView = Files.getFileAttributeView(file1, AclFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
+ assertNotNull("No ACL view for " + file1, aclView);
+
+ Map<String, ?> attrs = Files.readAttributes(file1, "acl:*", LinkOption.NOFOLLOW_LINKS);
+ outputDebugMessage("readAttributes(%s) %s", file1, attrs);
+ assertEquals("Mismatched owner for " + file1, aclView.getOwner(), attrs.get("owner"));
+
+ @SuppressWarnings("unchecked")
+ List<AclEntry> acl = (List<AclEntry>) attrs.get("acl");
+ outputDebugMessage("acls(%s) %s", file1, acl);
+ assertListEquals("Mismatched ACLs for " + file1, aclView.getAcl(), acl);
+ }
+
+ String remFile2Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-2.txt"));
+ Path file2 = fs.getPath(remFile2Path);
+ String remFile3Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder.resolve("file-3.txt"));
+ Path file3 = fs.getPath(remFile3Path);
+ try {
+ outputDebugMessage("Move with failure expected %s => %s", file2, file3);
+ Files.move(file2, file3, LinkOption.NOFOLLOW_LINKS);
+ fail("Unexpected success in moving " + file2 + " => " + file3);
+ } catch (NoSuchFileException e) {
+ // expected
+ }
+
+ Files.write(file2, "h".getBytes(StandardCharsets.UTF_8));
+ try {
+ outputDebugMessage("Move with failure expected %s => %s", file1, file2);
+ Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS);
+ fail("Unexpected success in moving " + file1 + " => " + file2);
+ } catch (FileAlreadyExistsException e) {
+ // expected
+ }
+
+ outputDebugMessage("Move with success expected %s => %s", file1, file2);
+ Files.move(file1, file2, LinkOption.NOFOLLOW_LINKS, StandardCopyOption.REPLACE_EXISTING);
+ outputDebugMessage("Move with success expected %s => %s", file2, file1);
+ Files.move(file2, file1, LinkOption.NOFOLLOW_LINKS);
+
+ Map<String, Object> attrs = Files.readAttributes(file1, "*");
+ outputDebugMessage("%s attributes: %s", file1, attrs);
+
+ // TODO there are many issues with symbolic links on Windows
+ if (OsUtils.isUNIX()) {
+ Path link = fs.getPath(remFile2Path);
+ Path linkParent = link.getParent();
+ Path relPath = linkParent.relativize(file1);
+ outputDebugMessage("Create symlink %s => %s", link, relPath);
+ 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());
+
+ outputDebugMessage("Delete symlink %s", link);
+ Files.delete(link);
+ }
+
+ attrs = Files.readAttributes(file1, "*", LinkOption.NOFOLLOW_LINKS);
+ outputDebugMessage("%s no-follow attributes: %s", file1, attrs);
+ assertEquals("Mismatched symlink data", expected, new String(Files.readAllBytes(file1), StandardCharsets.UTF_8));
+
+ try (FileChannel channel = FileChannel.open(file1)) {
+ try (FileLock lock = channel.lock()) {
+ outputDebugMessage("Lock %s: %s", file1, lock);
+
+ try (FileChannel channel2 = FileChannel.open(file1)) {
+ try (FileLock lock2 = channel2.lock()) {
+ fail("Unexpected success in re-locking " + file1 + ": " + lock2);
+ } catch (OverlappingFileLockException e) {
+ // expected
+ }
+ }
+ }
+ }
+
+ Files.delete(file1);
+ }
+
+ private URI createDefaultFileSystemURI() {
+ return createDefaultFileSystemURI(Collections.emptyMap());
+ }
+
+ private URI createDefaultFileSystemURI(Map<String, ?> params) {
+ return createFileSystemURI(getCurrentTestName(), params);
+ }
+
+ private URI createFileSystemURI(String username, Map<String, ?> params) {
+ return createFileSystemURI(username, port, params);
+ }
+
+ private static URI createFileSystemURI(String username, int port, Map<String, ?> params) {
+ return SftpFileSystemProvider.createFileSystemURI(TEST_LOCALHOST, port, username, username, params);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
new file mode 100644
index 0000000..a8bfaf8
--- /dev/null
+++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemURITest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.fs;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+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;
+
+/**
+ * TODO Add javadoc
+ *
+ * @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)
+@Category({ NoIoTestCase.class })
+public class SftpFileSystemURITest extends JUnitTestSupport {
+ private final String host;
+ private final int port;
+ private final String username;
+ private final String password;
+ private final Map<String, ?> params;
+
+ public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) {
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.params = params;
+ }
+
+ @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}")
+ public static List<Object[]> parameters() {
+ return new ArrayList<Object[]>() {
+ // Not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null});
+ add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)});
+ add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() {
+ // not serializing it
+ private static final long serialVersionUID = 1L;
+
+ {
+ put("param1", "1st");
+ put("param2", 2);
+ put("param3", false);
+ }
+ }
+ });
+ add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null});
+ }
+ };
+ }
+
+ @Test
+ public void testFullURIEncoding() {
+ URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params);
+ assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme());
+ assertEquals("Mismatched host", host, uri.getHost());
+ assertEquals("Mismatched port", port, uri.getPort());
+
+ BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri);
+ assertNotNull("No credentials provided", credentials);
+ assertEquals("Mismatched user", username, credentials.getUsername());
+ assertEquals("Mismatched password", password, credentials.getPassword());
+
+ Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri);
+ assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString()));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + "[host=" + host
+ + ", port=" + port
+ + ", username=" + username
+ + ", password=" + password
+ + ", params=" + params
+ + "]";
+ }
+}
[6/8] mina-sshd git commit: [SSHD-861] Added
SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
deleted file mode 100644
index f8cd75b..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java
+++ /dev/null
@@ -1,1312 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.channels.FileChannel;
-import java.nio.charset.Charset;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.FileSystemException;
-import java.nio.file.FileSystemNotFoundException;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.ProviderMismatchException;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.AclEntry;
-import java.nio.file.attribute.AclFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.PropertyResolver;
-import org.apache.sshd.common.PropertyResolverUtils;
-import org.apache.sshd.common.SshConstants;
-import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.auth.BasicCredentialsImpl;
-import org.apache.sshd.common.auth.BasicCredentialsProvider;
-import org.apache.sshd.common.auth.MutableBasicCredentials;
-import org.apache.sshd.common.io.IoSession;
-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.ValidateUtils;
-import org.apache.sshd.common.util.io.IoUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A registered {@link FileSystemProvider} that registers the "sftp://"
- * scheme so that URLs with this protocol are handled as remote SFTP {@link Path}-s
- * - e.g., "{@code sftp://user:password@host/remote/file/path}"
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpFileSystemProvider extends FileSystemProvider {
- public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
- public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
- public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
- public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
- public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
- public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
- public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
- public static final long DEFAULT_AUTH_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
- public static final String NAME_DECORDER_CHARSET_PROP_NAME = "sftp-fs-name-decoder-charset";
- public static final Charset DEFAULT_NAME_DECODER_CHARSET = SftpClient.DEFAULT_NAME_DECODING_CHARSET;
-
- /**
- * <P>
- * URI parameter that can be used to specify a special version selection. Options are:
- * </P>
- * <UL>
- * <LI>{@code max} - select maximum available version for the client</LI>
- * <LI>{@code min} - select minimum available version for the client</LI>
- * <LI>{@code current} - whatever version is reported by the server</LI>
- * <LI>{@code nnn} - select <U>only</U> the specified version</LI>
- * <LI>{@code a,b,c} - select one of the specified versions (if available) in preference order</LI>
- * </UL>
- */
- public static final String VERSION_PARAM = "version";
-
- public static final Set<Class<? extends FileAttributeView>> UNIVERSAL_SUPPORTED_VIEWS =
- Collections.unmodifiableSet(
- GenericUtils.asSet(
- PosixFileAttributeView.class,
- FileOwnerAttributeView.class,
- BasicFileAttributeView.class
- ));
-
- protected final Logger log;
-
- private final SshClient client;
- private final SftpClientFactory factory;
- private final SftpVersionSelector selector;
- private final NavigableMap<String, SftpFileSystem> fileSystems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
- public SftpFileSystemProvider() {
- this((SshClient) null);
- }
-
- public SftpFileSystemProvider(SftpVersionSelector selector) {
- this(null, selector);
- }
-
- /**
- * @param client The {@link SshClient} to use - if {@code null} then a
- * default one will be setup and started. Otherwise, it is assumed that
- * the client has already been started
- * @see SshClient#setUpDefaultClient()
- */
- public SftpFileSystemProvider(SshClient client) {
- this(client, SftpVersionSelector.CURRENT);
- }
-
- public SftpFileSystemProvider(SshClient client, SftpVersionSelector selector) {
- this(client, null, selector);
- }
-
- public SftpFileSystemProvider(SshClient client, SftpClientFactory factory, SftpVersionSelector selector) {
- this.log = LoggerFactory.getLogger(getClass());
- this.factory = factory;
- this.selector = selector;
- if (client == null) {
- // TODO: make this configurable using system properties
- client = SshClient.setUpDefaultClient();
- client.start();
- }
- this.client = client;
- }
-
- @Override
- public String getScheme() {
- return SftpConstants.SFTP_SUBSYSTEM_NAME;
- }
-
- public final SftpVersionSelector getSftpVersionSelector() {
- return selector;
- }
-
- @Override // NOTE: co-variant return
- public SftpFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided");
- int port = uri.getPort();
- if (port <= 0) {
- port = SshConstants.DEFAULT_PORT;
- }
-
- BasicCredentialsProvider credentials = parseCredentials(uri);
- ValidateUtils.checkState(credentials != null, "No credentials provided");
- String username = credentials.getUsername();
- String id = getFileSystemIdentifier(host, port, username);
- Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
- PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
- SftpVersionSelector selector = resolveSftpVersionSelector(uri, getSftpVersionSelector(), resolver);
- Charset decodingCharset =
- PropertyResolverUtils.getCharset(resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
- long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME);
- long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME);
- String password = credentials.getPassword();
-
- SftpFileSystem fileSystem;
- synchronized (fileSystems) {
- if (fileSystems.containsKey(id)) {
- throw new FileSystemAlreadyExistsException(id);
- }
-
- // TODO try and find a way to avoid doing this while locking the file systems cache
- ClientSession session = null;
- try {
- session = client.connect(username, host, port)
- .verify(maxConnectTime)
- .getSession();
- if (GenericUtils.size(params) > 0) {
- // Cannot use forEach because the session is not effectively final
- for (Map.Entry<String, ?> pe : params.entrySet()) {
- String key = pe.getKey();
- Object value = pe.getValue();
- if (VERSION_PARAM.equalsIgnoreCase(key)) {
- continue;
- }
-
- PropertyResolverUtils.updateProperty(session, key, value);
- }
-
- PropertyResolverUtils.updateProperty(session, SftpClient.NAME_DECODING_CHARSET, decodingCharset);
- }
-
- session.addPasswordIdentity(password);
- session.auth().verify(maxAuthTime);
-
- fileSystem = new SftpFileSystem(this, id, session, factory, selector);
- fileSystems.put(id, fileSystem);
- } catch (Exception e) {
- if (session != null) {
- try {
- session.close();
- } catch (IOException t) {
- if (log.isDebugEnabled()) {
- log.debug("Failed (" + t.getClass().getSimpleName() + ")"
- + " to close session for new file system on " + host + ":" + port
- + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
- + ": " + t.getMessage());
- }
- }
- }
-
- if (e instanceof IOException) {
- throw (IOException) e;
- } else if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- } else {
- throw new IOException(e);
- }
- }
- }
-
- fileSystem.setReadBufferSize(resolver.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
- fileSystem.setWriteBufferSize(resolver.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
- if (log.isDebugEnabled()) {
- log.debug("newFileSystem({}): {}", uri.toASCIIString(), fileSystem);
- }
- return fileSystem;
- }
-
- protected SftpVersionSelector resolveSftpVersionSelector(URI uri, SftpVersionSelector defaultSelector, PropertyResolver resolver) {
- String preference = resolver.getString(VERSION_PARAM);
- if (GenericUtils.isEmpty(preference)) {
- return defaultSelector;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("resolveSftpVersionSelector({}) preference={}", uri, preference);
- }
-
- if ("max".equalsIgnoreCase(preference)) {
- return SftpVersionSelector.MAXIMUM;
- } else if ("min".equalsIgnoreCase(preference)) {
- return SftpVersionSelector.MINIMUM;
- } else if ("current".equalsIgnoreCase(preference)) {
- return SftpVersionSelector.CURRENT;
- }
-
- String[] values = GenericUtils.split(preference, ',');
- if (values.length == 1) {
- return SftpVersionSelector.fixedVersionSelector(Integer.parseInt(values[0]));
- }
-
- int[] preferred = new int[values.length];
- for (int index = 0; index < values.length; index++) {
- preferred[index] = Integer.parseInt(values[index]);
- }
-
- return SftpVersionSelector.preferredVersionSelector(preferred);
- }
-
- // NOTE: URI parameters override environment ones
- public static Map<String, Object> resolveFileSystemParameters(Map<String, ?> env, Map<String, Object> uriParams) {
- if (GenericUtils.isEmpty(env)) {
- return GenericUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
- } else if (GenericUtils.isEmpty(uriParams)) {
- return Collections.unmodifiableMap(env);
- }
-
- Map<String, Object> resolved = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- resolved.putAll(env);
- resolved.putAll(uriParams);
- return resolved;
- }
-
- /**
- * Attempts to parse the user information from the URI
- *
- * @param uri The {@link URI} value - ignored if {@code null} or does not
- * contain any {@link URI#getUserInfo() user info}.
- * @return The parsed credentials - {@code null} if none available
- */
- public static MutableBasicCredentials parseCredentials(URI uri) {
- return parseCredentials((uri == null) ? "" : uri.getUserInfo());
- }
-
- public static MutableBasicCredentials parseCredentials(String userInfo) {
- if (GenericUtils.isEmpty(userInfo)) {
- return null;
- }
-
- String[] ui = GenericUtils.split(userInfo, ':');
- ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
- return new BasicCredentialsImpl(ui[0], ui[1]);
- }
-
- public static Map<String, Object> parseURIParameters(URI uri) {
- return parseURIParameters((uri == null) ? "" : uri.getQuery());
- }
-
- public static Map<String, Object> parseURIParameters(String params) {
- if (GenericUtils.isEmpty(params)) {
- return Collections.emptyMap();
- }
-
- if (params.charAt(0) == '?') {
- if (params.length() == 1) {
- return Collections.emptyMap();
- }
- params = params.substring(1);
- }
-
- String[] pairs = GenericUtils.split(params, '&');
- Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- for (String p : pairs) {
- int pos = p.indexOf('=');
- if (pos < 0) {
- map.put(p, Boolean.TRUE);
- continue;
- }
-
- String key = p.substring(0, pos);
- String value = p.substring(pos + 1);
- if (NumberUtils.isIntegerNumber(value)) {
- map.put(key, Long.valueOf(value));
- } else if ("true".equals(value) || "false".equals("value")) {
- map.put(key, Boolean.valueOf(value));
- } else {
- map.put(key, value);
- }
- }
-
- return map;
- }
-
- public SftpFileSystem newFileSystem(ClientSession session) throws IOException {
- String id = getFileSystemIdentifier(session);
- SftpFileSystem fileSystem;
- synchronized (fileSystems) {
- if (fileSystems.containsKey(id)) {
- throw new FileSystemAlreadyExistsException(id);
- }
- fileSystem = new SftpFileSystem(this, id, session, factory, getSftpVersionSelector());
- fileSystems.put(id, fileSystem);
- }
-
- fileSystem.setReadBufferSize(session.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
- fileSystem.setWriteBufferSize(session.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
- if (log.isDebugEnabled()) {
- log.debug("newFileSystem: {}", fileSystem);
- }
-
- return fileSystem;
- }
-
- @Override
- public FileSystem getFileSystem(URI uri) {
- String id = getFileSystemIdentifier(uri);
- SftpFileSystem fs = getFileSystem(id);
- if (fs == null) {
- throw new FileSystemNotFoundException(id);
- }
- return fs;
- }
-
- /**
- * @param id File system identifier - ignored if {@code null}/empty
- * @return The removed {@link SftpFileSystem} - {@code null} if no match
- */
- public SftpFileSystem removeFileSystem(String id) {
- if (GenericUtils.isEmpty(id)) {
- return null;
- }
-
- SftpFileSystem removed;
- synchronized (fileSystems) {
- removed = fileSystems.remove(id);
- }
-
- if (log.isDebugEnabled()) {
- log.debug("removeFileSystem({}): {}", id, removed);
- }
- return removed;
- }
-
- /**
- * @param id File system identifier - ignored if {@code null}/empty
- * @return The cached {@link SftpFileSystem} - {@code null} if no match
- */
- public SftpFileSystem getFileSystem(String id) {
- if (GenericUtils.isEmpty(id)) {
- return null;
- }
-
- synchronized (fileSystems) {
- return fileSystems.get(id);
- }
- }
-
- @Override
- public Path getPath(URI uri) {
- FileSystem fs = getFileSystem(uri);
- return fs.getPath(uri.getPath());
- }
-
- @Override
- public FileChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
- return newFileChannel(path, options, attrs);
- }
-
- @Override
- public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
- Collection<SftpClient.OpenMode> modes = SftpClient.OpenMode.fromOpenOptions(options);
- if (modes.isEmpty()) {
- modes = EnumSet.of(SftpClient.OpenMode.Read, SftpClient.OpenMode.Write);
- }
- // TODO: process file attributes
- return new SftpFileSystemChannel(toSftpPath(path), modes);
- }
-
- @Override
- public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
- final SftpPath p = toSftpPath(dir);
- return new SftpDirectoryStream(p);
- }
-
- @Override
- public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
- SftpPath p = toSftpPath(dir);
- SftpFileSystem fs = p.getFileSystem();
- if (log.isDebugEnabled()) {
- log.debug("createDirectory({}) {} ({})", fs, dir, Arrays.asList(attrs));
- }
- try (SftpClient sftp = fs.getClient()) {
- try {
- sftp.mkdir(dir.toString());
- } catch (SftpException e) {
- int sftpStatus = e.getStatus();
- if ((sftp.getVersion() == SftpConstants.SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
- try {
- Attributes attributes = sftp.stat(dir.toString());
- if (attributes != null) {
- throw new FileAlreadyExistsException(p.toString());
- }
- } catch (SshException e2) {
- e.addSuppressed(e2);
- }
- }
- if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
- throw new FileAlreadyExistsException(p.toString());
- }
- throw e;
- }
- for (FileAttribute<?> attr : attrs) {
- setAttribute(p, attr.name(), attr.value());
- }
- }
- }
-
- @Override
- public void delete(Path path) throws IOException {
- SftpPath p = toSftpPath(path);
- checkAccess(p, AccessMode.WRITE);
-
- SftpFileSystem fs = p.getFileSystem();
- if (log.isDebugEnabled()) {
- log.debug("delete({}) {}", fs, path);
- }
-
- try (SftpClient sftp = fs.getClient()) {
- BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
- if (attributes.isDirectory()) {
- sftp.rmdir(path.toString());
- } else {
- sftp.remove(path.toString());
- }
- }
- }
-
- @Override
- public void copy(Path source, Path target, CopyOption... options) throws IOException {
- SftpPath src = toSftpPath(source);
- SftpPath dst = toSftpPath(target);
- if (src.getFileSystem() != dst.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
- }
- checkAccess(src);
-
- boolean replaceExisting = false;
- boolean copyAttributes = false;
- boolean noFollowLinks = false;
- for (CopyOption opt : options) {
- replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
- copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
- noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
- }
- LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
-
- // attributes of source file
- BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
- if (attrs.isSymbolicLink()) {
- throw new IOException("Copying of symbolic links not supported");
- }
-
- // delete target if it exists and REPLACE_EXISTING is specified
- Boolean status = IoUtils.checkFileExists(target, linkOptions);
- if (status == null) {
- throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
- }
-
- if (log.isDebugEnabled()) {
- log.debug("copy({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
- }
-
- if (replaceExisting) {
- deleteIfExists(target);
- } else {
- if (status) {
- throw new FileAlreadyExistsException(target.toString());
- }
- }
-
- // create directory or copy file
- if (attrs.isDirectory()) {
- createDirectory(target);
- } else {
- try (InputStream in = newInputStream(source);
- OutputStream os = newOutputStream(target)) {
- IoUtils.copy(in, os);
- }
- }
-
- // copy basic attributes to target
- if (copyAttributes) {
- BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
- try {
- view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
- } catch (Throwable x) {
- // rollback
- try {
- delete(target);
- } catch (Throwable suppressed) {
- x.addSuppressed(suppressed);
- }
- throw x;
- }
- }
- }
-
- @Override
- public void move(Path source, Path target, CopyOption... options) throws IOException {
- SftpPath src = toSftpPath(source);
- SftpFileSystem fsSrc = src.getFileSystem();
- SftpPath dst = toSftpPath(target);
-
- if (src.getFileSystem() != dst.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
- }
- checkAccess(src);
-
- boolean replaceExisting = false;
- boolean copyAttributes = false;
- boolean noFollowLinks = false;
- for (CopyOption opt : options) {
- replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
- copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
- noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
- }
- LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
-
- // attributes of source file
- BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
- if (attrs.isSymbolicLink()) {
- throw new IOException("Moving of source symbolic link (" + source + ") to " + target + " not supported");
- }
-
- // delete target if it exists and REPLACE_EXISTING is specified
- Boolean status = IoUtils.checkFileExists(target, linkOptions);
- if (status == null) {
- throw new AccessDeniedException("Existence cannot be determined for move target " + target);
- }
-
- if (log.isDebugEnabled()) {
- log.debug("move({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
- }
-
- if (replaceExisting) {
- deleteIfExists(target);
- } else if (status) {
- throw new FileAlreadyExistsException(target.toString());
- }
-
- try (SftpClient sftp = fsSrc.getClient()) {
- sftp.rename(src.toString(), dst.toString());
- }
-
- // copy basic attributes to target
- if (copyAttributes) {
- BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
- try {
- view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
- } catch (Throwable x) {
- // rollback
- try {
- delete(target);
- } catch (Throwable suppressed) {
- x.addSuppressed(suppressed);
- }
- throw x;
- }
- }
- }
-
- @Override
- public boolean isSameFile(Path path1, Path path2) throws IOException {
- SftpPath p1 = toSftpPath(path1);
- SftpPath p2 = toSftpPath(path2);
- if (p1.getFileSystem() != p2.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
- }
- checkAccess(p1);
- checkAccess(p2);
- return p1.equals(p2);
- }
-
- @Override
- public boolean isHidden(Path path) throws IOException {
- return false;
- }
-
- @Override
- public FileStore getFileStore(Path path) throws IOException {
- FileSystem fs = path.getFileSystem();
- if (!(fs instanceof SftpFileSystem)) {
- throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") path not attached to an SFTP file system");
- }
-
- SftpFileSystem sftpFs = (SftpFileSystem) fs;
- String id = sftpFs.getId();
- SftpFileSystem cached = getFileSystem(id);
- if (cached != sftpFs) {
- throw new FileSystemException(path.toString(), path.toString(), "Mismatched file system instance for id=" + id);
- }
-
- return sftpFs.getFileStores().get(0);
- }
-
- @Override
- public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
- SftpPath l = toSftpPath(link);
- SftpFileSystem fsLink = l.getFileSystem();
- SftpPath t = toSftpPath(target);
- if (fsLink != t.getFileSystem()) {
- throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
- }
-
- if (log.isDebugEnabled()) {
- log.debug("createSymbolicLink({})[{}] {} => {}", fsLink, Arrays.asList(attrs), link, target);
- }
-
- try (SftpClient client = fsLink.getClient()) {
- client.symLink(l.toString(), t.toString());
- }
- }
-
- @Override
- public Path readSymbolicLink(Path link) throws IOException {
- SftpPath l = toSftpPath(link);
- SftpFileSystem fsLink = l.getFileSystem();
- try (SftpClient client = fsLink.getClient()) {
- String linkPath = client.readLink(l.toString());
- if (log.isDebugEnabled()) {
- log.debug("readSymbolicLink({})[{}] {} => {}", fsLink, link, linkPath);
- }
-
- return fsLink.getPath(linkPath);
- }
- }
-
- @Override
- public void checkAccess(Path path, AccessMode... modes) throws IOException {
- SftpPath p = toSftpPath(path);
- boolean w = false;
- boolean x = false;
- if (GenericUtils.length(modes) > 0) {
- for (AccessMode mode : modes) {
- switch (mode) {
- case READ:
- break;
- case WRITE:
- w = true;
- break;
- case EXECUTE:
- x = true;
- break;
- default:
- throw new UnsupportedOperationException("Unsupported mode: " + mode);
- }
- }
- }
-
- BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
- if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
- throw new NoSuchFileException(path.toString());
- }
-
- SftpFileSystem fs = p.getFileSystem();
- if (x || (w && fs.isReadOnly())) {
- throw new AccessDeniedException("Filesystem is read-only: " + path.toString());
- }
- }
-
- @Override
- public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, final LinkOption... options) {
- if (isSupportedFileAttributeView(path, type)) {
- if (AclFileAttributeView.class.isAssignableFrom(type)) {
- return type.cast(new SftpAclFileAttributeView(this, path, options));
- } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
- return type.cast(new SftpPosixFileAttributeView(this, path, options));
- }
- }
-
- throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
- }
-
- public boolean isSupportedFileAttributeView(Path path, Class<? extends FileAttributeView> type) {
- return isSupportedFileAttributeView(toSftpPath(path).getFileSystem(), type);
- }
-
- public boolean isSupportedFileAttributeView(SftpFileSystem fs, Class<? extends FileAttributeView> type) {
- Collection<String> views = fs.supportedFileAttributeViews();
- if ((type == null) || GenericUtils.isEmpty(views)) {
- return false;
- } else if (PosixFileAttributeView.class.isAssignableFrom(type)) {
- return views.contains("posix");
- } else if (AclFileAttributeView.class.isAssignableFrom(type)) {
- return views.contains("acl"); // must come before owner view
- } else if (FileOwnerAttributeView.class.isAssignableFrom(type)) {
- return views.contains("owner");
- } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
- return views.contains("basic"); // must be last
- } else {
- return false;
- }
- }
-
- @Override
- public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
- if (type.isAssignableFrom(PosixFileAttributes.class)) {
- return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
- }
-
- throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
- }
-
- @Override
- public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
- String view;
- String attrs;
- int i = attributes.indexOf(':');
- if (i == -1) {
- view = "basic";
- attrs = attributes;
- } else {
- view = attributes.substring(0, i++);
- attrs = attributes.substring(i);
- }
-
- return readAttributes(path, view, attrs, options);
- }
-
- public Map<String, Object> readAttributes(Path path, String view, String attrs, LinkOption... options) throws IOException {
- SftpPath p = toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- Collection<String> views = fs.supportedFileAttributeViews();
- if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
- throw new UnsupportedOperationException("readAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported: " + views);
- }
-
- if ("basic".equalsIgnoreCase(view) || "posix".equalsIgnoreCase(view) || "owner".equalsIgnoreCase(view)) {
- return readPosixViewAttributes(p, view, attrs, options);
- } else if ("acl".equalsIgnoreCase(view)) {
- return readAclViewAttributes(p, view, attrs, options);
- } else {
- return readCustomViewAttributes(p, view, attrs, options);
- }
- }
-
- protected Map<String, Object> readCustomViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
- throw new UnsupportedOperationException("readCustomViewAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported");
- }
-
- protected NavigableMap<String, Object> readAclViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
- if ("*".equals(attrs)) {
- attrs = "acl,owner";
- }
-
- SftpFileSystem fs = path.getFileSystem();
- SftpClient.Attributes attributes;
- try (SftpClient client = fs.getClient()) {
- attributes = readRemoteAttributes(path, options);
- }
-
- NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- String[] attrValues = GenericUtils.split(attrs, ',');
- boolean traceEnabled = log.isTraceEnabled();
- for (String attr : attrValues) {
- switch (attr) {
- case "acl":
- List<AclEntry> acl = attributes.getAcl();
- if (acl != null) {
- map.put(attr, acl);
- }
- break;
- case "owner":
- String owner = attributes.getOwner();
- if (GenericUtils.length(owner) > 0) {
- map.put(attr, new SftpFileSystem.DefaultUserPrincipal(owner));
- }
- break;
- default:
- if (traceEnabled) {
- log.trace("readAclViewAttributes({})[{}] unknown attribute: {}", fs, attrs, attr);
- }
- }
- }
-
- return map;
- }
-
- public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
- SftpFileSystem fs = path.getFileSystem();
- try (SftpClient client = fs.getClient()) {
- try {
- SftpClient.Attributes attrs;
- if (IoUtils.followLinks(options)) {
- attrs = client.stat(path.toString());
- } else {
- attrs = client.lstat(path.toString());
- }
- if (log.isTraceEnabled()) {
- log.trace("readRemoteAttributes({})[{}]: {}", fs, path, attrs);
- }
- return attrs;
- } catch (SftpException e) {
- if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
- throw new NoSuchFileException(path.toString());
- }
- throw e;
- }
- }
- }
-
- protected NavigableMap<String, Object> readPosixViewAttributes(
- SftpPath path, String view, String attrs, LinkOption... options)
- throws IOException {
- PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
- if ("*".equals(attrs)) {
- attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
- }
-
- NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- boolean traceEnabled = log.isTraceEnabled();
- String[] attrValues = GenericUtils.split(attrs, ',');
- for (String attr : attrValues) {
- switch (attr) {
- case "lastModifiedTime":
- map.put(attr, v.lastModifiedTime());
- break;
- case "lastAccessTime":
- map.put(attr, v.lastAccessTime());
- break;
- case "creationTime":
- map.put(attr, v.creationTime());
- break;
- case "size":
- map.put(attr, v.size());
- break;
- case "isRegularFile":
- map.put(attr, v.isRegularFile());
- break;
- case "isDirectory":
- map.put(attr, v.isDirectory());
- break;
- case "isSymbolicLink":
- map.put(attr, v.isSymbolicLink());
- break;
- case "isOther":
- map.put(attr, v.isOther());
- break;
- case "fileKey":
- map.put(attr, v.fileKey());
- break;
- case "owner":
- map.put(attr, v.owner());
- break;
- case "permissions":
- map.put(attr, v.permissions());
- break;
- case "group":
- map.put(attr, v.group());
- break;
- default:
- if (traceEnabled) {
- log.trace("readPosixViewAttributes({})[{}:{}] ignored for {}", path, view, attr, attrs);
- }
- }
- }
- return map;
- }
-
- @Override
- public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
- String view;
- String attr;
- int i = attribute.indexOf(':');
- if (i == -1) {
- view = "basic";
- attr = attribute;
- } else {
- view = attribute.substring(0, i++);
- attr = attribute.substring(i);
- }
-
- setAttribute(path, view, attr, value, options);
- }
-
- public void setAttribute(Path path, String view, String attr, Object value, LinkOption... options) throws IOException {
- SftpPath p = toSftpPath(path);
- SftpFileSystem fs = p.getFileSystem();
- Collection<String> views = fs.supportedFileAttributeViews();
- if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
- throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] view " + view + " not supported: " + views);
- }
-
- SftpClient.Attributes attributes = new SftpClient.Attributes();
- switch (attr) {
- case "lastModifiedTime":
- attributes.modifyTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "lastAccessTime":
- attributes.accessTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "creationTime":
- attributes.createTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
- break;
- case "size":
- attributes.size(((Number) value).longValue());
- break;
- case "permissions": {
- @SuppressWarnings("unchecked")
- Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
- attributes.perms(attributesToPermissions(path, attrSet));
- break;
- }
- case "owner":
- attributes.owner(((UserPrincipal) value).getName());
- break;
- case "group":
- attributes.group(((GroupPrincipal) value).getName());
- break;
- case "acl": {
- ValidateUtils.checkTrue("acl".equalsIgnoreCase(view), "ACL cannot be set via view=%s", view);
- @SuppressWarnings("unchecked")
- List<AclEntry> acl = (List<AclEntry>) value;
- attributes.acl(acl);
- break;
- }
- case "isRegularFile":
- case "isDirectory":
- case "isSymbolicLink":
- case "isOther":
- case "fileKey":
- throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] modification N/A");
- default:
- if (log.isTraceEnabled()) {
- log.trace("setAttribute({})[{}] ignore {}:{}={}", fs, path, view, attr, value);
- }
- }
-
- if (log.isDebugEnabled()) {
- log.debug("setAttribute({}) {}: {}", fs, path, attributes);
- }
-
- try (SftpClient client = fs.getClient()) {
- client.setStat(p.toString(), attributes);
- }
- }
-
- public SftpPath toSftpPath(Path path) {
- Objects.requireNonNull(path, "No path provided");
- if (!(path instanceof SftpPath)) {
- throw new ProviderMismatchException("Path is not SFTP: " + path);
- }
- return (SftpPath) path;
- }
-
- protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
- if (GenericUtils.isEmpty(perms)) {
- return 0;
- }
-
- int pf = 0;
- boolean traceEnabled = log.isTraceEnabled();
- for (PosixFilePermission p : perms) {
- switch (p) {
- case OWNER_READ:
- pf |= SftpConstants.S_IRUSR;
- break;
- case OWNER_WRITE:
- pf |= SftpConstants.S_IWUSR;
- break;
- case OWNER_EXECUTE:
- pf |= SftpConstants.S_IXUSR;
- break;
- case GROUP_READ:
- pf |= SftpConstants.S_IRGRP;
- break;
- case GROUP_WRITE:
- pf |= SftpConstants.S_IWGRP;
- break;
- case GROUP_EXECUTE:
- pf |= SftpConstants.S_IXGRP;
- break;
- case OTHERS_READ:
- pf |= SftpConstants.S_IROTH;
- break;
- case OTHERS_WRITE:
- pf |= SftpConstants.S_IWOTH;
- break;
- case OTHERS_EXECUTE:
- pf |= SftpConstants.S_IXOTH;
- break;
- default:
- if (traceEnabled) {
- log.trace("attributesToPermissions(" + path + ") ignored " + p);
- }
- }
- }
-
- return pf;
- }
-
- public static String getRWXPermissions(int perms) {
- StringBuilder sb = new StringBuilder(10 /* 3 * rwx + (d)irectory */);
- if ((perms & SftpConstants.S_IFLNK) == SftpConstants.S_IFLNK) {
- sb.append('l');
- } else if ((perms & SftpConstants.S_IFDIR) == SftpConstants.S_IFDIR) {
- sb.append('d');
- } else {
- sb.append('-');
- }
-
- if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
- sb.append('r');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
- sb.append('w');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
- sb.append('x');
- } else {
- sb.append('-');
- }
-
- if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
- sb.append('r');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
- sb.append('w');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
- sb.append('x');
- } else {
- sb.append('-');
- }
-
- if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
- sb.append('r');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
- sb.append('w');
- } else {
- sb.append('-');
- }
- if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
- sb.append('x');
- } else {
- sb.append('-');
- }
-
- return sb.toString();
- }
-
- public static String getOctalPermissions(int perms) {
- Collection<PosixFilePermission> attrs = permissionsToAttributes(perms);
- return getOctalPermissions(attrs);
- }
-
- public static Set<PosixFilePermission> permissionsToAttributes(int perms) {
- Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
- if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
- p.add(PosixFilePermission.OWNER_READ);
- }
- if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
- p.add(PosixFilePermission.OWNER_WRITE);
- }
- if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
- p.add(PosixFilePermission.OWNER_EXECUTE);
- }
- if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
- p.add(PosixFilePermission.GROUP_READ);
- }
- if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
- p.add(PosixFilePermission.GROUP_WRITE);
- }
- if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
- p.add(PosixFilePermission.GROUP_EXECUTE);
- }
- if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
- p.add(PosixFilePermission.OTHERS_READ);
- }
- if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
- p.add(PosixFilePermission.OTHERS_WRITE);
- }
- if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
- p.add(PosixFilePermission.OTHERS_EXECUTE);
- }
- return p;
- }
-
- public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
- int pf = 0;
-
- for (PosixFilePermission p : perms) {
- switch (p) {
- case OWNER_READ:
- pf |= SftpConstants.S_IRUSR;
- break;
- case OWNER_WRITE:
- pf |= SftpConstants.S_IWUSR;
- break;
- case OWNER_EXECUTE:
- pf |= SftpConstants.S_IXUSR;
- break;
- case GROUP_READ:
- pf |= SftpConstants.S_IRGRP;
- break;
- case GROUP_WRITE:
- pf |= SftpConstants.S_IWGRP;
- break;
- case GROUP_EXECUTE:
- pf |= SftpConstants.S_IXGRP;
- break;
- case OTHERS_READ:
- pf |= SftpConstants.S_IROTH;
- break;
- case OTHERS_WRITE:
- pf |= SftpConstants.S_IWOTH;
- break;
- case OTHERS_EXECUTE:
- pf |= SftpConstants.S_IXOTH;
- break;
- default: // ignored
- }
- }
-
- return String.format("%04o", pf);
- }
-
- /**
- * Uses the host, port and username to create a unique identifier
- *
- * @param uri The {@link URI} - <B>Note:</B> not checked to make sure
- * that the scheme is {@code sftp://}
- * @return The unique identifier
- * @see #getFileSystemIdentifier(String, int, String)
- */
- public static String getFileSystemIdentifier(URI uri) {
- String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
- String[] ui = GenericUtils.split(userInfo, ':');
- ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
- return getFileSystemIdentifier(uri.getHost(), uri.getPort(), ui[0]);
- }
-
- /**
- * Uses the remote host address, port and current username to create a unique identifier
- *
- * @param session The {@link ClientSession}
- * @return The unique identifier
- * @see #getFileSystemIdentifier(String, int, String)
- */
- public static String getFileSystemIdentifier(ClientSession session) {
- IoSession ioSession = session.getIoSession();
- SocketAddress addr = ioSession.getRemoteAddress();
- String username = session.getUsername();
- if (addr instanceof InetSocketAddress) {
- InetSocketAddress inetAddr = (InetSocketAddress) addr;
- return getFileSystemIdentifier(inetAddr.getHostString(), inetAddr.getPort(), username);
- } else {
- return getFileSystemIdentifier(addr.toString(), SshConstants.DEFAULT_PORT, username);
- }
- }
-
- public static String getFileSystemIdentifier(String host, int port, String username) {
- return GenericUtils.trimToEmpty(host) + ':'
- + ((port <= 0) ? SshConstants.DEFAULT_PORT : port) + ':'
- + GenericUtils.trimToEmpty(username);
- }
-
- public static URI createFileSystemURI(String host, int port, String username, String password) {
- return createFileSystemURI(host, port, username, password, Collections.emptyMap());
- }
-
- public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
- ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
-
- String queryPart = null;
- int numParams = GenericUtils.size(params);
- if (numParams > 0) {
- StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
- for (Map.Entry<String, ?> pe : params.entrySet()) {
- String key = pe.getKey();
- Object value = pe.getValue();
- if (sb.length() > 0) {
- sb.append('&');
- }
- sb.append(key);
- if (value != null) {
- sb.append('=').append(Objects.toString(value, null));
- }
- }
-
- queryPart = sb.toString();
- }
-
- try {
- String userAuth = encodeCredentials(username, password);
- return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
- + " to create access URI: " + e.getMessage(), e);
- }
- }
-
- public static String encodeCredentials(String username, String password) {
- ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
- ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
- /*
- * There is no way to properly encode/decode credentials that already contain
- * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
- *
- *
- * Use of the format "user:password" in the userinfo field is
- * deprecated. Applications should not render as clear text any data
- * after the first colon (":") character found within a userinfo
- * subcomponent unless the data after the colon is the empty string
- * (indicating no password). Applications may choose to ignore or
- * reject such data when it is received as part of a reference and
- * should reject the storage of such data in unencrypted form.
- */
- ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
- return username + ":" + password;
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
deleted file mode 100644
index 5567b58..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPath.java
+++ /dev/null
@@ -1,43 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.List;
-
-import org.apache.sshd.common.file.util.BasePath;
-
-public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
- public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
- super(fileSystem, root, names);
- }
-
- @Override
- public SftpPath toRealPath(LinkOption... options) throws IOException {
- // TODO: handle links
- SftpPath absolute = toAbsolutePath();
- FileSystem fs = getFileSystem();
- FileSystemProvider provider = fs.provider();
- provider.checkAccess(absolute);
- return absolute;
- }
-}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
deleted file mode 100644
index 49b4b48..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPathIterator.java
+++ /dev/null
@@ -1,82 +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.subsystem.sftp;
-
-import java.nio.file.Path;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPathIterator implements Iterator<Path> {
- private final SftpPath p;
- private final Iterator<? extends SftpClient.DirEntry> it;
- private boolean dotIgnored;
- private boolean dotdotIgnored;
- private SftpClient.DirEntry curEntry;
-
- public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) {
- this(path, (iter == null) ? null : iter.iterator());
- }
-
- public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) {
- p = path;
- it = iter;
- curEntry = nextEntry();
- }
-
- @Override
- public boolean hasNext() {
- return curEntry != null;
- }
-
- @Override
- public Path next() {
- if (curEntry == null) {
- throw new NoSuchElementException("No next entry");
- }
-
- SftpClient.DirEntry entry = curEntry;
- curEntry = nextEntry();
- return p.resolve(entry.getFilename());
- }
-
- private SftpClient.DirEntry nextEntry() {
- while ((it != null) && it.hasNext()) {
- SftpClient.DirEntry entry = it.next();
- String name = entry.getFilename();
- if (".".equals(name) && (!dotIgnored)) {
- dotIgnored = true;
- } else if ("..".equals(name) && (!dotdotIgnored)) {
- dotdotIgnored = true;
- } else {
- return entry;
- }
- }
-
- return null;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
deleted file mode 100644
index 1fb614c..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributeView.java
+++ /dev/null
@@ -1,94 +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.subsystem.sftp;
-
-import java.io.IOException;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.Set;
-
-import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPosixFileAttributeView extends AbstractSftpFileAttributeView implements PosixFileAttributeView {
- public SftpPosixFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
- super(provider, path, options);
- }
-
- @Override
- public String name() {
- return "posix";
- }
-
- @Override
- public PosixFileAttributes readAttributes() throws IOException {
- return new SftpPosixFileAttributes(path, readRemoteAttributes());
- }
-
- @Override
- public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
- SftpClient.Attributes attrs = new SftpClient.Attributes();
- if (lastModifiedTime != null) {
- attrs.modifyTime(lastModifiedTime);
- }
- if (lastAccessTime != null) {
- attrs.accessTime(lastAccessTime);
- }
- if (createTime != null) {
- attrs.createTime(createTime);
- }
-
- if (GenericUtils.isEmpty(attrs.getFlags())) {
- if (log.isDebugEnabled()) {
- log.debug("setTimes({}) no changes", path);
- }
- } else {
- writeRemoteAttributes(attrs);
- }
- }
-
- @Override
- public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
- provider.setAttribute(path, "permissions", perms, options);
- }
-
- @Override
- public void setGroup(GroupPrincipal group) throws IOException {
- provider.setAttribute(path, "group", group, options);
- }
-
- @Override
- public UserPrincipal getOwner() throws IOException {
- return readAttributes().owner();
- }
-
- @Override
- public void setOwner(UserPrincipal owner) throws IOException {
- provider.setAttribute(path, "owner", owner, options);
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
deleted file mode 100644
index a07e67f..0000000
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpPosixFileAttributes.java
+++ /dev/null
@@ -1,113 +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.subsystem.sftp;
-
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.GroupPrincipal;
-import java.nio.file.attribute.PosixFileAttributes;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.UserPrincipal;
-import java.util.Set;
-
-import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
-import org.apache.sshd.common.util.GenericUtils;
-
-/**
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public class SftpPosixFileAttributes implements PosixFileAttributes {
- private final Path path;
- private final Attributes attributes;
-
- public SftpPosixFileAttributes(Path path, Attributes attributes) {
- this.path = path;
- this.attributes = attributes;
- }
-
- /**
- * @return The referenced attributes file {@link Path}
- */
- public final Path getPath() {
- return path;
- }
-
- @Override
- public UserPrincipal owner() {
- String owner = attributes.getOwner();
- return GenericUtils.isEmpty(owner) ? null : new SftpFileSystem.DefaultUserPrincipal(owner);
- }
-
- @Override
- public GroupPrincipal group() {
- String group = attributes.getGroup();
- return GenericUtils.isEmpty(group) ? null : new SftpFileSystem.DefaultGroupPrincipal(group);
- }
-
- @Override
- public Set<PosixFilePermission> permissions() {
- return SftpFileSystemProvider.permissionsToAttributes(attributes.getPermissions());
- }
-
- @Override
- public FileTime lastModifiedTime() {
- return attributes.getModifyTime();
- }
-
- @Override
- public FileTime lastAccessTime() {
- return attributes.getAccessTime();
- }
-
- @Override
- public FileTime creationTime() {
- return attributes.getCreateTime();
- }
-
- @Override
- public boolean isRegularFile() {
- return attributes.isRegularFile();
- }
-
- @Override
- public boolean isDirectory() {
- return attributes.isDirectory();
- }
-
- @Override
- public boolean isSymbolicLink() {
- return attributes.isSymbolicLink();
- }
-
- @Override
- public boolean isOther() {
- return attributes.isOther();
- }
-
- @Override
- public long size() {
- return attributes.getSize();
- }
-
- @Override
- public Object fileKey() {
- // TODO consider implementing this
- return null;
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
new file mode 100644
index 0000000..fee19b0
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpAclFileAttributeView.java
@@ -0,0 +1,68 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.List;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpAclFileAttributeView extends AbstractSftpFileAttributeView implements AclFileAttributeView {
+ public SftpAclFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+ super(provider, path, options);
+ }
+
+ @Override
+ public UserPrincipal getOwner() throws IOException {
+ PosixFileAttributes v = provider.readAttributes(path, PosixFileAttributes.class, options);
+ return v.owner();
+ }
+
+ @Override
+ public void setOwner(UserPrincipal owner) throws IOException {
+ provider.setAttribute(path, "posix", "owner", owner, options);
+ }
+
+ @Override
+ public String name() {
+ return "acl";
+ }
+
+ @Override
+ public List<AclEntry> getAcl() throws IOException {
+ return readRemoteAttributes().getAcl();
+ }
+
+ @Override
+ public void setAcl(List<AclEntry> acl) throws IOException {
+ writeRemoteAttributes(new SftpClient.Attributes().acl(acl));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
new file mode 100644
index 0000000..f250af5
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpDirectoryStream.java
@@ -0,0 +1,67 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * Implements a remote {@link DirectoryStream}
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpDirectoryStream implements DirectoryStream<Path> {
+ private final SftpClient sftp;
+ private final Iterable<SftpClient.DirEntry> iter;
+ private final SftpPath p;
+
+ /**
+ * @param path The remote {@link SftpPath}
+ * @throws IOException If failed to initialize the directory access handle
+ */
+ public SftpDirectoryStream(SftpPath path) throws IOException {
+ SftpFileSystem fs = path.getFileSystem();
+ p = path;
+ sftp = fs.getClient();
+ iter = sftp.readDir(path.toString());
+ }
+
+ /**
+ * Client instance used to access the remote directory
+ *
+ * @return The {@link SftpClient} instance used to access the remote directory
+ */
+ public final SftpClient getClient() {
+ return sftp;
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return new SftpPathIterator(p, iter);
+ }
+
+ @Override
+ public void close() throws IOException {
+ sftp.close();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
new file mode 100644
index 0000000..a909e2d
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileStore.java
@@ -0,0 +1,105 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+import java.util.Collection;
+
+import org.apache.sshd.common.subsystem.sftp.SftpConstants;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileStore extends FileStore {
+ private final SftpFileSystem fs;
+ private final String name;
+
+ public SftpFileStore(String name, SftpFileSystem fs) {
+ this.name = name;
+ this.fs = fs;
+ }
+
+ public final SftpFileSystem getFileSystem() {
+ return fs;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String type() {
+ return SftpConstants.SFTP_SUBSYSTEM_NAME;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public long getTotalSpace() throws IOException {
+ return Long.MAX_VALUE; // TODO use SFTPv6 space-available extension
+ }
+
+ @Override
+ public long getUsableSpace() throws IOException {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public long getUnallocatedSpace() throws IOException {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+ SftpFileSystem sftpFs = getFileSystem();
+ SftpFileSystemProvider provider = sftpFs.provider();
+ return provider.isSupportedFileAttributeView(sftpFs, type);
+ }
+
+ @Override
+ public boolean supportsFileAttributeView(String name) {
+ if (GenericUtils.isEmpty(name)) {
+ return false; // debug breakpoint
+ }
+
+ FileSystem sftpFs = getFileSystem();
+ Collection<String> views = sftpFs.supportedFileAttributeViews();
+ return !GenericUtils.isEmpty(views) && views.contains(name);
+ }
+
+ @Override
+ public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+ return null; // no special views supported
+ }
+
+ @Override
+ public Object getAttribute(String attribute) throws IOException {
+ return null; // no special attributes supported
+ }
+}
[4/8] mina-sshd git commit: [SSHD-861] Added
SftpFileSystemClientSessionInitializer hook in SftpFileSystemProvider
Posted by lg...@apache.org.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
new file mode 100644
index 0000000..fdec2f3
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpFileSystemProvider.java
@@ -0,0 +1,1340 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileOwnerAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
+import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.common.PropertyResolver;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.auth.BasicCredentialsImpl;
+import org.apache.sshd.common.auth.BasicCredentialsProvider;
+import org.apache.sshd.common.auth.MutableBasicCredentials;
+import org.apache.sshd.common.io.IoSession;
+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.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A registered {@link FileSystemProvider} that registers the "sftp://"
+ * scheme so that URLs with this protocol are handled as remote SFTP {@link Path}-s
+ * - e.g., "{@code sftp://user:password@host/remote/file/path}"
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileSystemProvider extends FileSystemProvider {
+ public static final String READ_BUFFER_PROP_NAME = "sftp-fs-read-buffer-size";
+ public static final int DEFAULT_READ_BUFFER_SIZE = SftpClient.DEFAULT_READ_BUFFER_SIZE;
+ public static final String WRITE_BUFFER_PROP_NAME = "sftp-fs-write-buffer-size";
+ public static final int DEFAULT_WRITE_BUFFER_SIZE = SftpClient.DEFAULT_WRITE_BUFFER_SIZE;
+ public static final String CONNECT_TIME_PROP_NAME = "sftp-fs-connect-time";
+ public static final long DEFAULT_CONNECT_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+ public static final String AUTH_TIME_PROP_NAME = "sftp-fs-auth-time";
+ public static final long DEFAULT_AUTH_TIME = SftpClient.DEFAULT_WAIT_TIMEOUT;
+ public static final String NAME_DECORDER_CHARSET_PROP_NAME = "sftp-fs-name-decoder-charset";
+ public static final Charset DEFAULT_NAME_DECODER_CHARSET = SftpClient.DEFAULT_NAME_DECODING_CHARSET;
+
+ /**
+ * <P>
+ * URI parameter that can be used to specify a special version selection. Options are:
+ * </P>
+ * <UL>
+ * <LI>{@code max} - select maximum available version for the client</LI>
+ * <LI>{@code min} - select minimum available version for the client</LI>
+ * <LI>{@code current} - whatever version is reported by the server</LI>
+ * <LI>{@code nnn} - select <U>only</U> the specified version</LI>
+ * <LI>{@code a,b,c} - select one of the specified versions (if available) in preference order</LI>
+ * </UL>
+ */
+ public static final String VERSION_PARAM = "version";
+
+ public static final Set<Class<? extends FileAttributeView>> UNIVERSAL_SUPPORTED_VIEWS =
+ Collections.unmodifiableSet(
+ GenericUtils.asSet(
+ PosixFileAttributeView.class,
+ FileOwnerAttributeView.class,
+ BasicFileAttributeView.class
+ ));
+
+ protected final Logger log;
+
+ private final SshClient clientInstance;
+ private final SftpClientFactory factory;
+ private final SftpVersionSelector versionSelector;
+ private final NavigableMap<String, SftpFileSystem> fileSystems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ private SftpFileSystemClientSessionInitializer fsSessionInitializer = SftpFileSystemClientSessionInitializer.DEFAULT;
+
+ public SftpFileSystemProvider() {
+ this((SshClient) null);
+ }
+
+ public SftpFileSystemProvider(SftpVersionSelector selector) {
+ this(null, selector);
+ }
+
+ /**
+ * @param client The {@link SshClient} to use - if {@code null} then a
+ * default one will be setup and started. Otherwise, it is assumed that
+ * the client has already been started
+ * @see SshClient#setUpDefaultClient()
+ */
+ public SftpFileSystemProvider(SshClient client) {
+ this(client, SftpVersionSelector.CURRENT);
+ }
+
+ public SftpFileSystemProvider(SshClient client, SftpVersionSelector selector) {
+ this(client, null, selector);
+ }
+
+ public SftpFileSystemProvider(SshClient client, SftpClientFactory factory, SftpVersionSelector selector) {
+ this.log = LoggerFactory.getLogger(getClass());
+ this.factory = factory;
+ this.versionSelector = selector;
+ if (client == null) {
+ // TODO: make this configurable using system properties
+ client = SshClient.setUpDefaultClient();
+ client.start();
+ }
+ this.clientInstance = client;
+ }
+
+ @Override
+ public String getScheme() {
+ return SftpConstants.SFTP_SUBSYSTEM_NAME;
+ }
+
+ public final SftpVersionSelector getSftpVersionSelector() {
+ return versionSelector;
+ }
+
+ public final SshClient getClientInstance() {
+ return clientInstance;
+ }
+
+ public SftpClientFactory getSftpClientFactory() {
+ return factory;
+ }
+
+ public SftpFileSystemClientSessionInitializer getSftpFileSystemClientSessionInitializer() {
+ return fsSessionInitializer;
+ }
+
+ public void setSftpFileSystemClientSessionInitializer(SftpFileSystemClientSessionInitializer initializer) {
+ fsSessionInitializer = Objects.requireNonNull(initializer, "No initializer provided");
+ }
+
+ @Override // NOTE: co-variant return
+ public SftpFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+ String host = ValidateUtils.checkNotNullAndNotEmpty(uri.getHost(), "Host not provided");
+ int port = uri.getPort();
+ if (port <= 0) {
+ port = SshConstants.DEFAULT_PORT;
+ }
+
+ BasicCredentialsProvider credentials = parseCredentials(uri);
+ ValidateUtils.checkState(credentials != null, "No credentials provided");
+
+ String username = credentials.getUsername();
+ String id = getFileSystemIdentifier(host, port, username);
+ SftpFileSystemInitializationContext context = new SftpFileSystemInitializationContext(id, uri, env);
+ context.setHost(host);
+ context.setPort(port);
+ context.setCredentials(credentials);
+
+ Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri));
+ PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params);
+ context.setPropertyResolver(resolver);
+ context.setMaxConnectTime(resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME));
+ context.setMaxAuthTime(resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME));
+
+ SftpVersionSelector selector = resolveSftpVersionSelector(uri, getSftpVersionSelector(), resolver);
+ Charset decodingCharset = PropertyResolverUtils.getCharset(
+ resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET);
+
+ SftpFileSystemClientSessionInitializer initializer = getSftpFileSystemClientSessionInitializer();
+ SftpFileSystem fileSystem;
+ synchronized (fileSystems) {
+ if (fileSystems.containsKey(id)) {
+ throw new FileSystemAlreadyExistsException(id);
+ }
+
+ // TODO try and find a way to avoid doing this while locking the file systems cache
+ ClientSession session = null;
+ try {
+ session = initializer.createClientSession(this, context);
+
+ // Make any extra configuration parameters available to the session
+ if (GenericUtils.size(params) > 0) {
+ // Cannot use forEach because the session is not effectively final
+ for (Map.Entry<String, ?> pe : params.entrySet()) {
+ String key = pe.getKey();
+ Object value = pe.getValue();
+ if (VERSION_PARAM.equalsIgnoreCase(key)) {
+ continue;
+ }
+
+ PropertyResolverUtils.updateProperty(session, key, value);
+ }
+
+ PropertyResolverUtils.updateProperty(session, SftpClient.NAME_DECODING_CHARSET, decodingCharset);
+ }
+
+ initializer.authenticateClientSession(this, context, session);
+
+ fileSystem = initializer.createSftpFileSystem(this, context, session, selector);
+ fileSystems.put(id, fileSystem);
+ } catch (Exception e) {
+ if (session != null) {
+ try {
+ session.close();
+ } catch (IOException t) {
+ if (log.isDebugEnabled()) {
+ log.debug("Failed (" + t.getClass().getSimpleName() + ")"
+ + " to close session for new file system on " + host + ":" + port
+ + " due to " + e.getClass().getSimpleName() + "[" + e.getMessage() + "]"
+ + ": " + t.getMessage());
+ }
+ }
+ }
+
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ fileSystem.setReadBufferSize(resolver.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+ fileSystem.setWriteBufferSize(resolver.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+ if (log.isDebugEnabled()) {
+ log.debug("newFileSystem({}): {}", uri.toASCIIString(), fileSystem);
+ }
+ return fileSystem;
+ }
+
+ protected SftpVersionSelector resolveSftpVersionSelector(URI uri, SftpVersionSelector defaultSelector, PropertyResolver resolver) {
+ String preference = resolver.getString(VERSION_PARAM);
+ if (GenericUtils.isEmpty(preference)) {
+ return defaultSelector;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("resolveSftpVersionSelector({}) preference={}", uri, preference);
+ }
+
+ if ("max".equalsIgnoreCase(preference)) {
+ return SftpVersionSelector.MAXIMUM;
+ } else if ("min".equalsIgnoreCase(preference)) {
+ return SftpVersionSelector.MINIMUM;
+ } else if ("current".equalsIgnoreCase(preference)) {
+ return SftpVersionSelector.CURRENT;
+ }
+
+ String[] values = GenericUtils.split(preference, ',');
+ if (values.length == 1) {
+ return SftpVersionSelector.fixedVersionSelector(Integer.parseInt(values[0]));
+ }
+
+ int[] preferred = new int[values.length];
+ for (int index = 0; index < values.length; index++) {
+ preferred[index] = Integer.parseInt(values[index]);
+ }
+
+ return SftpVersionSelector.preferredVersionSelector(preferred);
+ }
+
+ // NOTE: URI parameters override environment ones
+ public static Map<String, Object> resolveFileSystemParameters(Map<String, ?> env, Map<String, Object> uriParams) {
+ if (GenericUtils.isEmpty(env)) {
+ return GenericUtils.isEmpty(uriParams) ? Collections.emptyMap() : uriParams;
+ } else if (GenericUtils.isEmpty(uriParams)) {
+ return Collections.unmodifiableMap(env);
+ }
+
+ Map<String, Object> resolved = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ resolved.putAll(env);
+ resolved.putAll(uriParams);
+ return resolved;
+ }
+
+ /**
+ * Attempts to parse the user information from the URI
+ *
+ * @param uri The {@link URI} value - ignored if {@code null} or does not
+ * contain any {@link URI#getUserInfo() user info}.
+ * @return The parsed credentials - {@code null} if none available
+ */
+ public static MutableBasicCredentials parseCredentials(URI uri) {
+ return parseCredentials((uri == null) ? "" : uri.getUserInfo());
+ }
+
+ public static MutableBasicCredentials parseCredentials(String userInfo) {
+ if (GenericUtils.isEmpty(userInfo)) {
+ return null;
+ }
+
+ String[] ui = GenericUtils.split(userInfo, ':');
+ ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+ return new BasicCredentialsImpl(ui[0], ui[1]);
+ }
+
+ public static Map<String, Object> parseURIParameters(URI uri) {
+ return parseURIParameters((uri == null) ? "" : uri.getQuery());
+ }
+
+ public static Map<String, Object> parseURIParameters(String params) {
+ if (GenericUtils.isEmpty(params)) {
+ return Collections.emptyMap();
+ }
+
+ if (params.charAt(0) == '?') {
+ if (params.length() == 1) {
+ return Collections.emptyMap();
+ }
+ params = params.substring(1);
+ }
+
+ String[] pairs = GenericUtils.split(params, '&');
+ Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ for (String p : pairs) {
+ int pos = p.indexOf('=');
+ if (pos < 0) {
+ map.put(p, Boolean.TRUE);
+ continue;
+ }
+
+ String key = p.substring(0, pos);
+ String value = p.substring(pos + 1);
+ if (NumberUtils.isIntegerNumber(value)) {
+ map.put(key, Long.valueOf(value));
+ } else if ("true".equals(value) || "false".equals("value")) {
+ map.put(key, Boolean.valueOf(value));
+ } else {
+ map.put(key, value);
+ }
+ }
+
+ return map;
+ }
+
+ public SftpFileSystem newFileSystem(ClientSession session) throws IOException {
+ String id = getFileSystemIdentifier(session);
+ SftpFileSystem fileSystem;
+ synchronized (fileSystems) {
+ if (fileSystems.containsKey(id)) {
+ throw new FileSystemAlreadyExistsException(id);
+ }
+ fileSystem = new SftpFileSystem(this, id, session, factory, getSftpVersionSelector());
+ fileSystems.put(id, fileSystem);
+ }
+
+ fileSystem.setReadBufferSize(session.getIntProperty(READ_BUFFER_PROP_NAME, DEFAULT_READ_BUFFER_SIZE));
+ fileSystem.setWriteBufferSize(session.getIntProperty(WRITE_BUFFER_PROP_NAME, DEFAULT_WRITE_BUFFER_SIZE));
+ if (log.isDebugEnabled()) {
+ log.debug("newFileSystem: {}", fileSystem);
+ }
+
+ return fileSystem;
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ String id = getFileSystemIdentifier(uri);
+ SftpFileSystem fs = getFileSystem(id);
+ if (fs == null) {
+ throw new FileSystemNotFoundException(id);
+ }
+ return fs;
+ }
+
+ /**
+ * @param id File system identifier - ignored if {@code null}/empty
+ * @return The removed {@link SftpFileSystem} - {@code null} if no match
+ */
+ public SftpFileSystem removeFileSystem(String id) {
+ if (GenericUtils.isEmpty(id)) {
+ return null;
+ }
+
+ SftpFileSystem removed;
+ synchronized (fileSystems) {
+ removed = fileSystems.remove(id);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("removeFileSystem({}): {}", id, removed);
+ }
+ return removed;
+ }
+
+ /**
+ * @param id File system identifier - ignored if {@code null}/empty
+ * @return The cached {@link SftpFileSystem} - {@code null} if no match
+ */
+ public SftpFileSystem getFileSystem(String id) {
+ if (GenericUtils.isEmpty(id)) {
+ return null;
+ }
+
+ synchronized (fileSystems) {
+ return fileSystems.get(id);
+ }
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ FileSystem fs = getFileSystem(uri);
+ return fs.getPath(uri.getPath());
+ }
+
+ @Override
+ public FileChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ return newFileChannel(path, options, attrs);
+ }
+
+ @Override
+ public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ Collection<SftpClient.OpenMode> modes = SftpClient.OpenMode.fromOpenOptions(options);
+ if (modes.isEmpty()) {
+ modes = EnumSet.of(SftpClient.OpenMode.Read, SftpClient.OpenMode.Write);
+ }
+ // TODO: process file attributes
+ return new SftpFileSystemChannel(toSftpPath(path), modes);
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+ final SftpPath p = toSftpPath(dir);
+ return new SftpDirectoryStream(p);
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ SftpPath p = toSftpPath(dir);
+ SftpFileSystem fs = p.getFileSystem();
+ if (log.isDebugEnabled()) {
+ log.debug("createDirectory({}) {} ({})", fs, dir, Arrays.asList(attrs));
+ }
+ try (SftpClient sftp = fs.getClient()) {
+ try {
+ sftp.mkdir(dir.toString());
+ } catch (SftpException e) {
+ int sftpStatus = e.getStatus();
+ if ((sftp.getVersion() == SftpConstants.SFTP_V3) && (sftpStatus == SftpConstants.SSH_FX_FAILURE)) {
+ try {
+ Attributes attributes = sftp.stat(dir.toString());
+ if (attributes != null) {
+ throw new FileAlreadyExistsException(p.toString());
+ }
+ } catch (SshException e2) {
+ e.addSuppressed(e2);
+ }
+ }
+ if (sftpStatus == SftpConstants.SSH_FX_FILE_ALREADY_EXISTS) {
+ throw new FileAlreadyExistsException(p.toString());
+ }
+ throw e;
+ }
+ for (FileAttribute<?> attr : attrs) {
+ setAttribute(p, attr.name(), attr.value());
+ }
+ }
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ SftpPath p = toSftpPath(path);
+ checkAccess(p, AccessMode.WRITE);
+
+ SftpFileSystem fs = p.getFileSystem();
+ if (log.isDebugEnabled()) {
+ log.debug("delete({}) {}", fs, path);
+ }
+
+ try (SftpClient sftp = fs.getClient()) {
+ BasicFileAttributes attributes = readAttributes(path, BasicFileAttributes.class);
+ if (attributes.isDirectory()) {
+ sftp.rmdir(path.toString());
+ } else {
+ sftp.remove(path.toString());
+ }
+ }
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ SftpPath src = toSftpPath(source);
+ SftpPath dst = toSftpPath(target);
+ if (src.getFileSystem() != dst.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+ }
+ checkAccess(src);
+
+ boolean replaceExisting = false;
+ boolean copyAttributes = false;
+ boolean noFollowLinks = false;
+ for (CopyOption opt : options) {
+ replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+ copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+ noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+ }
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(!noFollowLinks);
+
+ // attributes of source file
+ BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+ if (attrs.isSymbolicLink()) {
+ throw new IOException("Copying of symbolic links not supported");
+ }
+
+ // delete target if it exists and REPLACE_EXISTING is specified
+ Boolean status = IoUtils.checkFileExists(target, linkOptions);
+ if (status == null) {
+ throw new AccessDeniedException("Existence cannot be determined for copy target: " + target);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("copy({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
+ }
+
+ if (replaceExisting) {
+ deleteIfExists(target);
+ } else {
+ if (status) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ }
+
+ // create directory or copy file
+ if (attrs.isDirectory()) {
+ createDirectory(target);
+ } else {
+ try (InputStream in = newInputStream(source);
+ OutputStream os = newOutputStream(target)) {
+ IoUtils.copy(in, os);
+ }
+ }
+
+ // copy basic attributes to target
+ if (copyAttributes) {
+ BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+ try {
+ view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+ } catch (Throwable x) {
+ // rollback
+ try {
+ delete(target);
+ } catch (Throwable suppressed) {
+ x.addSuppressed(suppressed);
+ }
+ throw x;
+ }
+ }
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ SftpPath src = toSftpPath(source);
+ SftpFileSystem fsSrc = src.getFileSystem();
+ SftpPath dst = toSftpPath(target);
+
+ if (src.getFileSystem() != dst.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + src + " vs. " + dst);
+ }
+ checkAccess(src);
+
+ boolean replaceExisting = false;
+ boolean copyAttributes = false;
+ boolean noFollowLinks = false;
+ for (CopyOption opt : options) {
+ replaceExisting |= opt == StandardCopyOption.REPLACE_EXISTING;
+ copyAttributes |= opt == StandardCopyOption.COPY_ATTRIBUTES;
+ noFollowLinks |= opt == LinkOption.NOFOLLOW_LINKS;
+ }
+ LinkOption[] linkOptions = IoUtils.getLinkOptions(noFollowLinks);
+
+ // attributes of source file
+ BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class, linkOptions);
+ if (attrs.isSymbolicLink()) {
+ throw new IOException("Moving of source symbolic link (" + source + ") to " + target + " not supported");
+ }
+
+ // delete target if it exists and REPLACE_EXISTING is specified
+ Boolean status = IoUtils.checkFileExists(target, linkOptions);
+ if (status == null) {
+ throw new AccessDeniedException("Existence cannot be determined for move target " + target);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("move({})[{}] {} => {}", src.getFileSystem(), Arrays.asList(options), src, dst);
+ }
+
+ if (replaceExisting) {
+ deleteIfExists(target);
+ } else if (status) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+
+ try (SftpClient sftp = fsSrc.getClient()) {
+ sftp.rename(src.toString(), dst.toString());
+ }
+
+ // copy basic attributes to target
+ if (copyAttributes) {
+ BasicFileAttributeView view = getFileAttributeView(target, BasicFileAttributeView.class, linkOptions);
+ try {
+ view.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime());
+ } catch (Throwable x) {
+ // rollback
+ try {
+ delete(target);
+ } catch (Throwable suppressed) {
+ x.addSuppressed(suppressed);
+ }
+ throw x;
+ }
+ }
+ }
+
+ @Override
+ public boolean isSameFile(Path path1, Path path2) throws IOException {
+ SftpPath p1 = toSftpPath(path1);
+ SftpPath p2 = toSftpPath(path2);
+ if (p1.getFileSystem() != p2.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + p1 + " vs. " + p2);
+ }
+ checkAccess(p1);
+ checkAccess(p2);
+ return p1.equals(p2);
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ return false;
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ FileSystem fs = path.getFileSystem();
+ if (!(fs instanceof SftpFileSystem)) {
+ throw new FileSystemException(path.toString(), path.toString(), "getFileStore(" + path + ") path not attached to an SFTP file system");
+ }
+
+ SftpFileSystem sftpFs = (SftpFileSystem) fs;
+ String id = sftpFs.getId();
+ SftpFileSystem cached = getFileSystem(id);
+ if (cached != sftpFs) {
+ throw new FileSystemException(path.toString(), path.toString(), "Mismatched file system instance for id=" + id);
+ }
+
+ return sftpFs.getFileStores().get(0);
+ }
+
+ @Override
+ public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
+ SftpPath l = toSftpPath(link);
+ SftpFileSystem fsLink = l.getFileSystem();
+ SftpPath t = toSftpPath(target);
+ if (fsLink != t.getFileSystem()) {
+ throw new ProviderMismatchException("Mismatched file system providers for " + l + " vs. " + t);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("createSymbolicLink({})[{}] {} => {}", fsLink, Arrays.asList(attrs), link, target);
+ }
+
+ try (SftpClient client = fsLink.getClient()) {
+ client.symLink(l.toString(), t.toString());
+ }
+ }
+
+ @Override
+ public Path readSymbolicLink(Path link) throws IOException {
+ SftpPath l = toSftpPath(link);
+ SftpFileSystem fsLink = l.getFileSystem();
+ try (SftpClient client = fsLink.getClient()) {
+ String linkPath = client.readLink(l.toString());
+ if (log.isDebugEnabled()) {
+ log.debug("readSymbolicLink({})[{}] {} => {}", fsLink, link, linkPath);
+ }
+
+ return fsLink.getPath(linkPath);
+ }
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ SftpPath p = toSftpPath(path);
+ boolean w = false;
+ boolean x = false;
+ if (GenericUtils.length(modes) > 0) {
+ for (AccessMode mode : modes) {
+ switch (mode) {
+ case READ:
+ break;
+ case WRITE:
+ w = true;
+ break;
+ case EXECUTE:
+ x = true;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported mode: " + mode);
+ }
+ }
+ }
+
+ BasicFileAttributes attrs = getFileAttributeView(p, BasicFileAttributeView.class).readAttributes();
+ if ((attrs == null) && !(p.isAbsolute() && p.getNameCount() == 0)) {
+ throw new NoSuchFileException(path.toString());
+ }
+
+ SftpFileSystem fs = p.getFileSystem();
+ if (x || (w && fs.isReadOnly())) {
+ throw new AccessDeniedException("Filesystem is read-only: " + path.toString());
+ }
+ }
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, final LinkOption... options) {
+ if (isSupportedFileAttributeView(path, type)) {
+ if (AclFileAttributeView.class.isAssignableFrom(type)) {
+ return type.cast(new SftpAclFileAttributeView(this, path, options));
+ } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
+ return type.cast(new SftpPosixFileAttributeView(this, path, options));
+ }
+ }
+
+ throw new UnsupportedOperationException("getFileAttributeView(" + path + ") view not supported: " + type.getSimpleName());
+ }
+
+ public boolean isSupportedFileAttributeView(Path path, Class<? extends FileAttributeView> type) {
+ return isSupportedFileAttributeView(toSftpPath(path).getFileSystem(), type);
+ }
+
+ public boolean isSupportedFileAttributeView(SftpFileSystem fs, Class<? extends FileAttributeView> type) {
+ Collection<String> views = fs.supportedFileAttributeViews();
+ if ((type == null) || GenericUtils.isEmpty(views)) {
+ return false;
+ } else if (PosixFileAttributeView.class.isAssignableFrom(type)) {
+ return views.contains("posix");
+ } else if (AclFileAttributeView.class.isAssignableFrom(type)) {
+ return views.contains("acl"); // must come before owner view
+ } else if (FileOwnerAttributeView.class.isAssignableFrom(type)) {
+ return views.contains("owner");
+ } else if (BasicFileAttributeView.class.isAssignableFrom(type)) {
+ return views.contains("basic"); // must be last
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+ if (type.isAssignableFrom(PosixFileAttributes.class)) {
+ return type.cast(getFileAttributeView(path, PosixFileAttributeView.class, options).readAttributes());
+ }
+
+ throw new UnsupportedOperationException("readAttributes(" + path + ")[" + type.getSimpleName() + "] N/A");
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+ String view;
+ String attrs;
+ int i = attributes.indexOf(':');
+ if (i == -1) {
+ view = "basic";
+ attrs = attributes;
+ } else {
+ view = attributes.substring(0, i++);
+ attrs = attributes.substring(i);
+ }
+
+ return readAttributes(path, view, attrs, options);
+ }
+
+ public Map<String, Object> readAttributes(Path path, String view, String attrs, LinkOption... options) throws IOException {
+ SftpPath p = toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ Collection<String> views = fs.supportedFileAttributeViews();
+ if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+ throw new UnsupportedOperationException("readAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported: " + views);
+ }
+
+ if ("basic".equalsIgnoreCase(view) || "posix".equalsIgnoreCase(view) || "owner".equalsIgnoreCase(view)) {
+ return readPosixViewAttributes(p, view, attrs, options);
+ } else if ("acl".equalsIgnoreCase(view)) {
+ return readAclViewAttributes(p, view, attrs, options);
+ } else {
+ return readCustomViewAttributes(p, view, attrs, options);
+ }
+ }
+
+ protected Map<String, Object> readCustomViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
+ throw new UnsupportedOperationException("readCustomViewAttributes(" + path + ")[" + view + ":" + attrs + "] view not supported");
+ }
+
+ protected NavigableMap<String, Object> readAclViewAttributes(SftpPath path, String view, String attrs, LinkOption... options) throws IOException {
+ if ("*".equals(attrs)) {
+ attrs = "acl,owner";
+ }
+
+ SftpFileSystem fs = path.getFileSystem();
+ SftpClient.Attributes attributes;
+ try (SftpClient client = fs.getClient()) {
+ attributes = readRemoteAttributes(path, options);
+ }
+
+ NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ String[] attrValues = GenericUtils.split(attrs, ',');
+ boolean traceEnabled = log.isTraceEnabled();
+ for (String attr : attrValues) {
+ switch (attr) {
+ case "acl":
+ List<AclEntry> acl = attributes.getAcl();
+ if (acl != null) {
+ map.put(attr, acl);
+ }
+ break;
+ case "owner":
+ String owner = attributes.getOwner();
+ if (GenericUtils.length(owner) > 0) {
+ map.put(attr, new SftpFileSystem.DefaultUserPrincipal(owner));
+ }
+ break;
+ default:
+ if (traceEnabled) {
+ log.trace("readAclViewAttributes({})[{}] unknown attribute: {}", fs, attrs, attr);
+ }
+ }
+ }
+
+ return map;
+ }
+
+ public SftpClient.Attributes readRemoteAttributes(SftpPath path, LinkOption... options) throws IOException {
+ SftpFileSystem fs = path.getFileSystem();
+ try (SftpClient client = fs.getClient()) {
+ try {
+ SftpClient.Attributes attrs;
+ if (IoUtils.followLinks(options)) {
+ attrs = client.stat(path.toString());
+ } else {
+ attrs = client.lstat(path.toString());
+ }
+ if (log.isTraceEnabled()) {
+ log.trace("readRemoteAttributes({})[{}]: {}", fs, path, attrs);
+ }
+ return attrs;
+ } catch (SftpException e) {
+ if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
+ throw new NoSuchFileException(path.toString());
+ }
+ throw e;
+ }
+ }
+ }
+
+ protected NavigableMap<String, Object> readPosixViewAttributes(
+ SftpPath path, String view, String attrs, LinkOption... options)
+ throws IOException {
+ PosixFileAttributes v = readAttributes(path, PosixFileAttributes.class, options);
+ if ("*".equals(attrs)) {
+ attrs = "lastModifiedTime,lastAccessTime,creationTime,size,isRegularFile,isDirectory,isSymbolicLink,isOther,fileKey,owner,permissions,group";
+ }
+
+ NavigableMap<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ boolean traceEnabled = log.isTraceEnabled();
+ String[] attrValues = GenericUtils.split(attrs, ',');
+ for (String attr : attrValues) {
+ switch (attr) {
+ case "lastModifiedTime":
+ map.put(attr, v.lastModifiedTime());
+ break;
+ case "lastAccessTime":
+ map.put(attr, v.lastAccessTime());
+ break;
+ case "creationTime":
+ map.put(attr, v.creationTime());
+ break;
+ case "size":
+ map.put(attr, v.size());
+ break;
+ case "isRegularFile":
+ map.put(attr, v.isRegularFile());
+ break;
+ case "isDirectory":
+ map.put(attr, v.isDirectory());
+ break;
+ case "isSymbolicLink":
+ map.put(attr, v.isSymbolicLink());
+ break;
+ case "isOther":
+ map.put(attr, v.isOther());
+ break;
+ case "fileKey":
+ map.put(attr, v.fileKey());
+ break;
+ case "owner":
+ map.put(attr, v.owner());
+ break;
+ case "permissions":
+ map.put(attr, v.permissions());
+ break;
+ case "group":
+ map.put(attr, v.group());
+ break;
+ default:
+ if (traceEnabled) {
+ log.trace("readPosixViewAttributes({})[{}:{}] ignored for {}", path, view, attr, attrs);
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+ String view;
+ String attr;
+ int i = attribute.indexOf(':');
+ if (i == -1) {
+ view = "basic";
+ attr = attribute;
+ } else {
+ view = attribute.substring(0, i++);
+ attr = attribute.substring(i);
+ }
+
+ setAttribute(path, view, attr, value, options);
+ }
+
+ public void setAttribute(Path path, String view, String attr, Object value, LinkOption... options) throws IOException {
+ SftpPath p = toSftpPath(path);
+ SftpFileSystem fs = p.getFileSystem();
+ Collection<String> views = fs.supportedFileAttributeViews();
+ if (GenericUtils.isEmpty(views) || (!views.contains(view))) {
+ throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] view " + view + " not supported: " + views);
+ }
+
+ SftpClient.Attributes attributes = new SftpClient.Attributes();
+ switch (attr) {
+ case "lastModifiedTime":
+ attributes.modifyTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "lastAccessTime":
+ attributes.accessTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "creationTime":
+ attributes.createTime((int) ((FileTime) value).to(TimeUnit.SECONDS));
+ break;
+ case "size":
+ attributes.size(((Number) value).longValue());
+ break;
+ case "permissions": {
+ @SuppressWarnings("unchecked")
+ Set<PosixFilePermission> attrSet = (Set<PosixFilePermission>) value;
+ attributes.perms(attributesToPermissions(path, attrSet));
+ break;
+ }
+ case "owner":
+ attributes.owner(((UserPrincipal) value).getName());
+ break;
+ case "group":
+ attributes.group(((GroupPrincipal) value).getName());
+ break;
+ case "acl": {
+ ValidateUtils.checkTrue("acl".equalsIgnoreCase(view), "ACL cannot be set via view=%s", view);
+ @SuppressWarnings("unchecked")
+ List<AclEntry> acl = (List<AclEntry>) value;
+ attributes.acl(acl);
+ break;
+ }
+ case "isRegularFile":
+ case "isDirectory":
+ case "isSymbolicLink":
+ case "isOther":
+ case "fileKey":
+ throw new UnsupportedOperationException("setAttribute(" + path + ")[" + view + ":" + attr + "=" + value + "] modification N/A");
+ default:
+ if (log.isTraceEnabled()) {
+ log.trace("setAttribute({})[{}] ignore {}:{}={}", fs, path, view, attr, value);
+ }
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("setAttribute({}) {}: {}", fs, path, attributes);
+ }
+
+ try (SftpClient client = fs.getClient()) {
+ client.setStat(p.toString(), attributes);
+ }
+ }
+
+ public SftpPath toSftpPath(Path path) {
+ Objects.requireNonNull(path, "No path provided");
+ if (!(path instanceof SftpPath)) {
+ throw new ProviderMismatchException("Path is not SFTP: " + path);
+ }
+ return (SftpPath) path;
+ }
+
+ protected int attributesToPermissions(Path path, Collection<PosixFilePermission> perms) {
+ if (GenericUtils.isEmpty(perms)) {
+ return 0;
+ }
+
+ int pf = 0;
+ boolean traceEnabled = log.isTraceEnabled();
+ for (PosixFilePermission p : perms) {
+ switch (p) {
+ case OWNER_READ:
+ pf |= SftpConstants.S_IRUSR;
+ break;
+ case OWNER_WRITE:
+ pf |= SftpConstants.S_IWUSR;
+ break;
+ case OWNER_EXECUTE:
+ pf |= SftpConstants.S_IXUSR;
+ break;
+ case GROUP_READ:
+ pf |= SftpConstants.S_IRGRP;
+ break;
+ case GROUP_WRITE:
+ pf |= SftpConstants.S_IWGRP;
+ break;
+ case GROUP_EXECUTE:
+ pf |= SftpConstants.S_IXGRP;
+ break;
+ case OTHERS_READ:
+ pf |= SftpConstants.S_IROTH;
+ break;
+ case OTHERS_WRITE:
+ pf |= SftpConstants.S_IWOTH;
+ break;
+ case OTHERS_EXECUTE:
+ pf |= SftpConstants.S_IXOTH;
+ break;
+ default:
+ if (traceEnabled) {
+ log.trace("attributesToPermissions(" + path + ") ignored " + p);
+ }
+ }
+ }
+
+ return pf;
+ }
+
+ public static String getRWXPermissions(int perms) {
+ StringBuilder sb = new StringBuilder(10 /* 3 * rwx + (d)irectory */);
+ if ((perms & SftpConstants.S_IFLNK) == SftpConstants.S_IFLNK) {
+ sb.append('l');
+ } else if ((perms & SftpConstants.S_IFDIR) == SftpConstants.S_IFDIR) {
+ sb.append('d');
+ } else {
+ sb.append('-');
+ }
+
+ if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
+ sb.append('r');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
+ sb.append('w');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
+ sb.append('x');
+ } else {
+ sb.append('-');
+ }
+
+ if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
+ sb.append('r');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
+ sb.append('w');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
+ sb.append('x');
+ } else {
+ sb.append('-');
+ }
+
+ if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
+ sb.append('r');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
+ sb.append('w');
+ } else {
+ sb.append('-');
+ }
+ if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
+ sb.append('x');
+ } else {
+ sb.append('-');
+ }
+
+ return sb.toString();
+ }
+
+ public static String getOctalPermissions(int perms) {
+ Collection<PosixFilePermission> attrs = permissionsToAttributes(perms);
+ return getOctalPermissions(attrs);
+ }
+
+ public static Set<PosixFilePermission> permissionsToAttributes(int perms) {
+ Set<PosixFilePermission> p = EnumSet.noneOf(PosixFilePermission.class);
+ if ((perms & SftpConstants.S_IRUSR) == SftpConstants.S_IRUSR) {
+ p.add(PosixFilePermission.OWNER_READ);
+ }
+ if ((perms & SftpConstants.S_IWUSR) == SftpConstants.S_IWUSR) {
+ p.add(PosixFilePermission.OWNER_WRITE);
+ }
+ if ((perms & SftpConstants.S_IXUSR) == SftpConstants.S_IXUSR) {
+ p.add(PosixFilePermission.OWNER_EXECUTE);
+ }
+ if ((perms & SftpConstants.S_IRGRP) == SftpConstants.S_IRGRP) {
+ p.add(PosixFilePermission.GROUP_READ);
+ }
+ if ((perms & SftpConstants.S_IWGRP) == SftpConstants.S_IWGRP) {
+ p.add(PosixFilePermission.GROUP_WRITE);
+ }
+ if ((perms & SftpConstants.S_IXGRP) == SftpConstants.S_IXGRP) {
+ p.add(PosixFilePermission.GROUP_EXECUTE);
+ }
+ if ((perms & SftpConstants.S_IROTH) == SftpConstants.S_IROTH) {
+ p.add(PosixFilePermission.OTHERS_READ);
+ }
+ if ((perms & SftpConstants.S_IWOTH) == SftpConstants.S_IWOTH) {
+ p.add(PosixFilePermission.OTHERS_WRITE);
+ }
+ if ((perms & SftpConstants.S_IXOTH) == SftpConstants.S_IXOTH) {
+ p.add(PosixFilePermission.OTHERS_EXECUTE);
+ }
+ return p;
+ }
+
+ public static String getOctalPermissions(Collection<PosixFilePermission> perms) {
+ int pf = 0;
+
+ for (PosixFilePermission p : perms) {
+ switch (p) {
+ case OWNER_READ:
+ pf |= SftpConstants.S_IRUSR;
+ break;
+ case OWNER_WRITE:
+ pf |= SftpConstants.S_IWUSR;
+ break;
+ case OWNER_EXECUTE:
+ pf |= SftpConstants.S_IXUSR;
+ break;
+ case GROUP_READ:
+ pf |= SftpConstants.S_IRGRP;
+ break;
+ case GROUP_WRITE:
+ pf |= SftpConstants.S_IWGRP;
+ break;
+ case GROUP_EXECUTE:
+ pf |= SftpConstants.S_IXGRP;
+ break;
+ case OTHERS_READ:
+ pf |= SftpConstants.S_IROTH;
+ break;
+ case OTHERS_WRITE:
+ pf |= SftpConstants.S_IWOTH;
+ break;
+ case OTHERS_EXECUTE:
+ pf |= SftpConstants.S_IXOTH;
+ break;
+ default: // ignored
+ }
+ }
+
+ return String.format("%04o", pf);
+ }
+
+ /**
+ * Uses the host, port and username to create a unique identifier
+ *
+ * @param uri The {@link URI} - <B>Note:</B> not checked to make sure
+ * that the scheme is {@code sftp://}
+ * @return The unique identifier
+ * @see #getFileSystemIdentifier(String, int, String)
+ */
+ public static String getFileSystemIdentifier(URI uri) {
+ String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided");
+ String[] ui = GenericUtils.split(userInfo, ':');
+ ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo);
+ return getFileSystemIdentifier(uri.getHost(), uri.getPort(), ui[0]);
+ }
+
+ /**
+ * Uses the remote host address, port and current username to create a unique identifier
+ *
+ * @param session The {@link ClientSession}
+ * @return The unique identifier
+ * @see #getFileSystemIdentifier(String, int, String)
+ */
+ public static String getFileSystemIdentifier(ClientSession session) {
+ IoSession ioSession = session.getIoSession();
+ SocketAddress addr = ioSession.getRemoteAddress();
+ String username = session.getUsername();
+ if (addr instanceof InetSocketAddress) {
+ InetSocketAddress inetAddr = (InetSocketAddress) addr;
+ return getFileSystemIdentifier(inetAddr.getHostString(), inetAddr.getPort(), username);
+ } else {
+ return getFileSystemIdentifier(addr.toString(), SshConstants.DEFAULT_PORT, username);
+ }
+ }
+
+ public static String getFileSystemIdentifier(String host, int port, String username) {
+ return GenericUtils.trimToEmpty(host) + ':'
+ + ((port <= 0) ? SshConstants.DEFAULT_PORT : port) + ':'
+ + GenericUtils.trimToEmpty(username);
+ }
+
+ public static URI createFileSystemURI(String host, int port, String username, String password) {
+ return createFileSystemURI(host, port, username, password, Collections.emptyMap());
+ }
+
+ public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) {
+ ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided");
+
+ String queryPart = null;
+ int numParams = GenericUtils.size(params);
+ if (numParams > 0) {
+ StringBuilder sb = new StringBuilder(numParams * Short.SIZE);
+ for (Map.Entry<String, ?> pe : params.entrySet()) {
+ String key = pe.getKey();
+ Object value = pe.getValue();
+ if (sb.length() > 0) {
+ sb.append('&');
+ }
+ sb.append(key);
+ if (value != null) {
+ sb.append('=').append(Objects.toString(value, null));
+ }
+ }
+
+ queryPart = sb.toString();
+ }
+
+ try {
+ String userAuth = encodeCredentials(username, password);
+ return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")"
+ + " to create access URI: " + e.getMessage(), e);
+ }
+ }
+
+ public static String encodeCredentials(String username, String password) {
+ ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided");
+ ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided");
+ /*
+ * There is no way to properly encode/decode credentials that already contain
+ * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1:
+ *
+ *
+ * Use of the format "user:password" in the userinfo field is
+ * deprecated. Applications should not render as clear text any data
+ * after the first colon (":") character found within a userinfo
+ * subcomponent unless the data after the colon is the empty string
+ * (indicating no password). Applications may choose to ignore or
+ * reject such data when it is received as part of a reference and
+ * should reject the storage of such data in unencrypted form.
+ */
+ ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials");
+ return username + ":" + password;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
new file mode 100644
index 0000000..381e272
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPath.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.client.subsystem.sftp.fs;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+
+import org.apache.sshd.common.file.util.BasePath;
+
+public class SftpPath extends BasePath<SftpPath, SftpFileSystem> {
+ public SftpPath(SftpFileSystem fileSystem, String root, List<String> names) {
+ super(fileSystem, root, names);
+ }
+
+ @Override
+ public SftpPath toRealPath(LinkOption... options) throws IOException {
+ // TODO: handle links
+ SftpPath absolute = toAbsolutePath();
+ FileSystem fs = getFileSystem();
+ FileSystemProvider provider = fs.provider();
+ provider.checkAccess(absolute);
+ return absolute;
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.java
new file mode 100644
index 0000000..8c4f89e
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPathIterator.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.fs;
+
+import java.nio.file.Path;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPathIterator implements Iterator<Path> {
+ private final SftpPath p;
+ private final Iterator<? extends SftpClient.DirEntry> it;
+ private boolean dotIgnored;
+ private boolean dotdotIgnored;
+ private SftpClient.DirEntry curEntry;
+
+ public SftpPathIterator(SftpPath path, Iterable<? extends SftpClient.DirEntry> iter) {
+ this(path, (iter == null) ? null : iter.iterator());
+ }
+
+ public SftpPathIterator(SftpPath path, Iterator<? extends SftpClient.DirEntry> iter) {
+ p = path;
+ it = iter;
+ curEntry = nextEntry();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return curEntry != null;
+ }
+
+ @Override
+ public Path next() {
+ if (curEntry == null) {
+ throw new NoSuchElementException("No next entry");
+ }
+
+ SftpClient.DirEntry entry = curEntry;
+ curEntry = nextEntry();
+ return p.resolve(entry.getFilename());
+ }
+
+ private SftpClient.DirEntry nextEntry() {
+ while ((it != null) && it.hasNext()) {
+ SftpClient.DirEntry entry = it.next();
+ String name = entry.getFilename();
+ if (".".equals(name) && (!dotIgnored)) {
+ dotIgnored = true;
+ } else if ("..".equals(name) && (!dotdotIgnored)) {
+ dotdotIgnored = true;
+ } else {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("newDirectoryStream(" + p + ") Iterator#remove() N/A");
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
new file mode 100644
index 0000000..276c4aa
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributeView.java
@@ -0,0 +1,95 @@
+/*
+ * 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.fs;
+
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient;
+import org.apache.sshd.client.subsystem.sftp.impl.AbstractSftpFileAttributeView;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributeView extends AbstractSftpFileAttributeView implements PosixFileAttributeView {
+ public SftpPosixFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) {
+ super(provider, path, options);
+ }
+
+ @Override
+ public String name() {
+ return "posix";
+ }
+
+ @Override
+ public PosixFileAttributes readAttributes() throws IOException {
+ return new SftpPosixFileAttributes(path, readRemoteAttributes());
+ }
+
+ @Override
+ public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+ SftpClient.Attributes attrs = new SftpClient.Attributes();
+ if (lastModifiedTime != null) {
+ attrs.modifyTime(lastModifiedTime);
+ }
+ if (lastAccessTime != null) {
+ attrs.accessTime(lastAccessTime);
+ }
+ if (createTime != null) {
+ attrs.createTime(createTime);
+ }
+
+ if (GenericUtils.isEmpty(attrs.getFlags())) {
+ if (log.isDebugEnabled()) {
+ log.debug("setTimes({}) no changes", path);
+ }
+ } else {
+ writeRemoteAttributes(attrs);
+ }
+ }
+
+ @Override
+ public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+ provider.setAttribute(path, "permissions", perms, options);
+ }
+
+ @Override
+ public void setGroup(GroupPrincipal group) throws IOException {
+ provider.setAttribute(path, "group", group, options);
+ }
+
+ @Override
+ public UserPrincipal getOwner() throws IOException {
+ return readAttributes().owner();
+ }
+
+ @Override
+ public void setOwner(UserPrincipal owner) throws IOException {
+ provider.setAttribute(path, "owner", owner, options);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
new file mode 100644
index 0000000..0c6c5fe
--- /dev/null
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/fs/SftpPosixFileAttributes.java
@@ -0,0 +1,113 @@
+/*
+ * 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.fs;
+
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Set;
+
+import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpPosixFileAttributes implements PosixFileAttributes {
+ private final Path path;
+ private final Attributes attributes;
+
+ public SftpPosixFileAttributes(Path path, Attributes attributes) {
+ this.path = path;
+ this.attributes = attributes;
+ }
+
+ /**
+ * @return The referenced attributes file {@link Path}
+ */
+ public final Path getPath() {
+ return path;
+ }
+
+ @Override
+ public UserPrincipal owner() {
+ String owner = attributes.getOwner();
+ return GenericUtils.isEmpty(owner) ? null : new SftpFileSystem.DefaultUserPrincipal(owner);
+ }
+
+ @Override
+ public GroupPrincipal group() {
+ String group = attributes.getGroup();
+ return GenericUtils.isEmpty(group) ? null : new SftpFileSystem.DefaultGroupPrincipal(group);
+ }
+
+ @Override
+ public Set<PosixFilePermission> permissions() {
+ return SftpFileSystemProvider.permissionsToAttributes(attributes.getPermissions());
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return attributes.getModifyTime();
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return attributes.getAccessTime();
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return attributes.getCreateTime();
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return attributes.isRegularFile();
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return attributes.isDirectory();
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return attributes.isSymbolicLink();
+ }
+
+ @Override
+ public boolean isOther() {
+ return attributes.isOther();
+ }
+
+ @Override
+ public long size() {
+ return attributes.getSize();
+ }
+
+ @Override
+ public Object fileKey() {
+ // TODO consider implementing this
+ return null;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
index 0fce423..8892e72 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/AbstractSftpFileAttributeView.java
@@ -27,9 +27,9 @@ import java.nio.file.attribute.FileAttributeView;
import java.util.Objects;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
-import org.apache.sshd.client.subsystem.sftp.SftpPath;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpPath;
import org.apache.sshd.common.subsystem.sftp.SftpConstants;
import org.apache.sshd.common.subsystem.sftp.SftpException;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/e3b8acd7/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
----------------------------------------------------------------------
diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
index c6702f8..ec65752 100644
--- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
+++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/impl/DefaultSftpClientFactory.java
@@ -25,9 +25,9 @@ import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
import org.apache.sshd.client.subsystem.sftp.SftpClientFactory;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.SftpFileSystemProvider;
import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
/**