[SSHD-790] Allow users to register a custom SFTP client factory
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/15428746 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/15428746 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/15428746 Branch: refs/heads/master Commit: 154287462cde88314cc7cd7c44978fc04a7c9b88 Parents: c5b163f Author: Goldstein Lyor <[email protected]> Authored: Thu Dec 28 10:37:12 2017 +0200 Committer: Goldstein Lyor <[email protected]> Committed: Thu Dec 28 11:06:38 2017 +0200 ---------------------------------------------------------------------- README.md | 111 ++ .../sshd/client/ClientFactoryManager.java | 2 + .../java/org/apache/sshd/client/SshClient.java | 13 + .../client/session/AbstractClientSession.java | 66 +- .../sshd/client/session/ClientSession.java | 3 +- .../subsystem/sftp/AbstractSftpClient.java | 1127 ----------------- .../sftp/AbstractSftpFileAttributeView.java | 88 -- .../subsystem/sftp/DefaultCloseableHandle.java | 65 - .../subsystem/sftp/DefaultSftpClient.java | 461 ------- .../sftp/SftpAclFileAttributeView.java | 2 + .../sshd/client/subsystem/sftp/SftpClient.java | 2 +- .../subsystem/sftp/SftpClientCreator.java | 20 +- .../subsystem/sftp/SftpClientFactory.java | 51 + .../sftp/SftpClientFactoryManager.java | 37 + .../client/subsystem/sftp/SftpFileSystem.java | 1 + .../subsystem/sftp/SftpFileSystemProvider.java | 2 +- .../sftp/SftpPosixFileAttributeView.java | 1 + .../subsystem/sftp/impl/AbstractSftpClient.java | 1134 ++++++++++++++++++ .../impl/AbstractSftpFileAttributeView.java | 92 ++ .../sftp/impl/DefaultCloseableHandle.java | 66 + .../subsystem/sftp/impl/DefaultSftpClient.java | 462 +++++++ .../sftp/impl/DefaultSftpClientFactory.java | 81 ++ .../sftp/DefaultCloseableHandleTest.java | 1 + 23 files changed, 2089 insertions(+), 1799 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index fb6560b..4e99d3e 100644 --- a/README.md +++ b/README.md @@ -543,6 +543,74 @@ configuration key. For more advanced restrictions one needs to sub-class `SftpSu `SftpSubsystemFactory` that uses the sub-classed code. +### Registering a custom `SftpClientFactory` + +The code creates `SftpClient`-s and `SftpFileSystem`-s using a default built-in `SftpClientFactory` instance (see +`DefaultSftpClientFactory`). Users may choose to register a custom factory in order to provide their own +implementations - e.g., in order to override some default behavior. The custom factory may be registered either at +the client or session level - e.g.: + +```java + + SshClient client = ... setup client... + client.setSftpClientFactory(new MySuperDuperSftpClientFactory()); + + try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) { + // override the default factory with a special one - but only for this session + session.setSftpClientFactory(new SpecialSessionSftpClientFactory()); + session.addPasswordIdentity(password); + session.auth.verify(timeout); + + try (SftpClient sftp = session.createSftpClient()) { + ... instance created through SpecialSessionSftpClientFactory ... + } + } + +``` + +If no factory provided or factory set to _null_ then code reverts to using the default built-in one. **Note:** setting +the factory to _null_ on the session level, simply delegates the creation to whatever factory is registered at the +client level - default or custom. + +```java + + SshClient client = ... setup client... + client.setSftpClientFactory(new MySuperDuperSftpClientFactory()); + + try (ClientSession session = client.connect(user, host, port).verify(timeout).getSession()) { + // override the default factory with a special one - but only for this session + session.setSftpClientFactory(new SpecialSessionSftpClientFactory()); + session.addPasswordIdentity(password); + session.auth.verify(timeout); + + try (SftpClient sftp = session.createSftpClient()) { + ... instance created through SpecialSessionSftpClientFactory ... + } + + // revert to one from client + session.setSftpClientFactory(null); + + try (SftpClient sftp = session.createSftpClient()) { + ... instance created through MySuperDuperSftpClientFactory ... + } + + // remove client-level factory + client.setSftpClientFactory(null); + + try (SftpClient sftp = session.createSftpClient()) { + ... instance created through built-in DefaultSftpClientFactory ... + } + + // re-instate session-level factory + session.setSftpClientFactory(new SpecialSessionSftpClientFactory()); + + try (SftpClient sftp = session.createSftpClient()) { + ... instance created through SpecialSessionSftpClientFactory ... + } + } + +``` + ### Using `SftpFileSystemProvider` to create an `SftpFileSystem` @@ -716,6 +784,49 @@ UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset. ``` +Another option is to register a custom `SftpClientFactory` and create a `DefaultSftpClient` that overrides `getReferencedName` method: + +```java + +public class MyCustomSftpClient extends DefaultSftpClient { + public MyCustomSftpClient(ClientSession session) { + super(session); + } + + @Override + protected String getReferencedName(int cmd, Buffer buf) { + byte[] bytes = buf.getBytes(); + Charset cs = detectCharset(bytes); + return new String(bytes, cs); + } +} + +public class MyCustomSftpClientFactory extends DefaultSftpClientFactory { + public MyCustomSftpClientFactory() { + super(); + } + + protected DefaultSftpClient createDefaultSftpClient(ClientSession session, SftpVersionSelector selector) throws IOException { + return MyCustomSftpClient(session); + } +} + + // Usage - register at client level and affect ALL SFTP interactions + SshClient client = ... setup/obtain an instance... + client.setSftpClientFactory(new MyCustomSftpClientFactory()); + + // Usage - selective session registration + SshClient client = ... setup/obtain an instance... + try (ClientSession session = client.connect(...)) { + if (...something special about the host/port/etc....) { + // affect only SFTP interactions for this session + session.setSftpClientFactory(new MyCustomSftpClientFactory()); + } + } + + +``` + ### Supported SFTP extensions Both client and server support several of the SFTP extensions specified in various drafts: http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java index 31b2a22..7627020 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java @@ -21,6 +21,7 @@ package org.apache.sshd.client; import org.apache.sshd.client.config.hosts.HostConfigEntryResolver; import org.apache.sshd.client.config.keys.ClientIdentityLoader; import org.apache.sshd.client.session.ClientProxyConnectorHolder; +import org.apache.sshd.client.subsystem.sftp.SftpClientFactoryManager; import org.apache.sshd.common.FactoryManager; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.scp.ScpFileOpenerHolder; @@ -33,6 +34,7 @@ import org.apache.sshd.common.scp.ScpFileOpenerHolder; */ public interface ClientFactoryManager extends FactoryManager, + SftpClientFactoryManager, ScpFileOpenerHolder, ClientProxyConnectorHolder, ClientAuthenticationManager { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java index 0e8979e..6c5c291 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java @@ -86,6 +86,8 @@ import org.apache.sshd.client.session.ClientUserAuthServiceFactory; import org.apache.sshd.client.session.SessionFactory; import org.apache.sshd.client.simple.AbstractSimpleClientSessionCreator; import org.apache.sshd.client.simple.SimpleClient; +import org.apache.sshd.client.subsystem.sftp.SftpClientFactory; +import org.apache.sshd.client.subsystem.sftp.impl.DefaultSftpClientFactory; import org.apache.sshd.common.Closeable; import org.apache.sshd.common.Factory; import org.apache.sshd.common.FactoryManager; @@ -211,6 +213,7 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa private FilePasswordProvider filePasswordProvider; private PasswordIdentityProvider passwordIdentityProvider; private ScpFileOpener scpOpener; + private SftpClientFactory sftpClientFactory; private final List<Object> identities = new CopyOnWriteArrayList<>(); private final AuthenticationIdentitiesProvider identitiesProvider; @@ -248,6 +251,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa } @Override + public SftpClientFactory getSftpClientFactory() { + return (sftpClientFactory == null) ? DefaultSftpClientFactory.INSTANCE : sftpClientFactory; + } + + @Override + public void setSftpClientFactory(SftpClientFactory sftpClientFactory) { + this.sftpClientFactory = sftpClientFactory; + } + + @Override public ServerKeyVerifier getServerKeyVerifier() { return serverKeyVerifier; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java index b67310d..bc44d58 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java @@ -43,10 +43,8 @@ import org.apache.sshd.client.channel.ClientChannel; import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.scp.DefaultScpClient; import org.apache.sshd.client.scp.ScpClient; -import org.apache.sshd.client.subsystem.sftp.DefaultSftpClient; 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.SftpClientFactory; import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector; import org.apache.sshd.common.FactoryManager; import org.apache.sshd.common.NamedFactory; @@ -92,6 +90,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C private ScpFileOpener scpOpener; private SocketAddress connectAddress; private ClientProxyConnector proxyConnector; + private SftpClientFactory sftpClientFactory; protected AbstractClientSession(ClientFactoryManager factoryManager, IoSession ioSession) { super(false, factoryManager, ioSession); @@ -168,6 +167,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C } @Override + public SftpClientFactory getSftpClientFactory() { + return resolveEffectiveProvider(SftpClientFactory.class, sftpClientFactory, getFactoryManager().getSftpClientFactory()); + } + + @Override + public void setSftpClientFactory(SftpClientFactory sftpClientFactory) { + this.sftpClientFactory = sftpClientFactory; + } + + @Override public void addPasswordIdentity(String password) { // DO NOT USE checkNotNullOrNotEmpty SINCE IT TRIMS THE RESULT ValidateUtils.checkTrue((password != null) && (!password.isEmpty()), "No password provided"); @@ -333,57 +342,14 @@ public abstract class AbstractClientSession extends AbstractSession implements C @Override public SftpClient createSftpClient(SftpVersionSelector selector) throws IOException { - DefaultSftpClient client = new DefaultSftpClient(this); - try { - client.negotiateVersion(selector); - } catch (IOException | RuntimeException e) { - if (log.isDebugEnabled()) { - log.debug("createSftpClient({}) failed ({}) to negotiate version: {}", - this, e.getClass().getSimpleName(), e.getMessage()); - } - if (log.isTraceEnabled()) { - log.trace("createSftpClient(" + this + ") version negotiation failure details", e); - } - - client.close(); - throw e; - } - - return client; - } - - @Override - public FileSystem createSftpFileSystem() throws IOException { - return createSftpFileSystem(SftpVersionSelector.CURRENT); - } - - @Override - public FileSystem createSftpFileSystem(int version) throws IOException { - return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version)); - } - - @Override - public FileSystem createSftpFileSystem(SftpVersionSelector selector) throws IOException { - return createSftpFileSystem(selector, SftpClient.DEFAULT_READ_BUFFER_SIZE, SftpClient.DEFAULT_WRITE_BUFFER_SIZE); - } - - @Override - public FileSystem createSftpFileSystem(int version, int readBufferSize, int writeBufferSize) throws IOException { - return createSftpFileSystem(SftpVersionSelector.fixedVersionSelector(version), readBufferSize, writeBufferSize); - } - - @Override - public FileSystem createSftpFileSystem(int readBufferSize, int writeBufferSize) throws IOException { - return createSftpFileSystem(SftpVersionSelector.CURRENT, readBufferSize, writeBufferSize); + SftpClientFactory factory = getSftpClientFactory(); + return factory.createSftpClient(this, selector); } @Override public FileSystem createSftpFileSystem(SftpVersionSelector selector, int readBufferSize, int writeBufferSize) throws IOException { - SftpFileSystemProvider provider = new SftpFileSystemProvider((org.apache.sshd.client.SshClient) getFactoryManager(), selector); - SftpFileSystem fs = provider.newFileSystem(this); - fs.setReadBufferSize(readBufferSize); - fs.setWriteBufferSize(writeBufferSize); - return fs; + SftpClientFactory factory = getSftpClientFactory(); + return factory.createSftpFileSystem(this, selector, readBufferSize, writeBufferSize); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java index 8c074f2..b807f17 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java @@ -46,6 +46,7 @@ import org.apache.sshd.client.scp.ScpClientCreator; import org.apache.sshd.client.session.forward.DynamicPortForwardingTracker; import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker; import org.apache.sshd.client.subsystem.sftp.SftpClientCreator; +import org.apache.sshd.client.subsystem.sftp.SftpClientFactoryManager; import org.apache.sshd.common.forward.PortForwardingManager; import org.apache.sshd.common.future.KeyExchangeFuture; import org.apache.sshd.common.session.Session; @@ -82,7 +83,7 @@ import org.apache.sshd.common.util.net.SshdSocketAddress; * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ public interface ClientSession - extends Session, ScpClientCreator, SftpClientCreator, + extends Session, ScpClientCreator, SftpClientCreator, SftpClientFactoryManager, ClientProxyConnectorHolder, ClientAuthenticationManager, PortForwardingManager { enum ClientSessionEvent { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java deleted file mode 100644 index fd2875c..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpClient.java +++ /dev/null @@ -1,1127 +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.charset.Charset; -import java.nio.file.attribute.FileTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.sshd.client.channel.ClientChannel; -import org.apache.sshd.client.subsystem.AbstractSubsystemClient; -import org.apache.sshd.client.subsystem.sftp.extensions.BuiltinSftpClientExtensions; -import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtension; -import org.apache.sshd.client.subsystem.sftp.extensions.SftpClientExtensionFactory; -import org.apache.sshd.common.SshException; -import org.apache.sshd.common.channel.Channel; -import org.apache.sshd.common.subsystem.sftp.SftpConstants; -import org.apache.sshd.common.subsystem.sftp.SftpException; -import org.apache.sshd.common.subsystem.sftp.SftpHelper; -import org.apache.sshd.common.subsystem.sftp.SftpUniversalOwnerAndGroup; -import org.apache.sshd.common.subsystem.sftp.extensions.ParserUtils; -import org.apache.sshd.common.util.GenericUtils; -import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.buffer.ByteArrayBuffer; - - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public abstract class AbstractSftpClient extends AbstractSubsystemClient implements SftpClient, RawSftpClient { - private final Attributes fileOpenAttributes = new Attributes(); - private final AtomicReference<Map<String, Object>> parsedExtensionsHolder = new AtomicReference<>(null); - - protected AbstractSftpClient() { - fileOpenAttributes.setType(SftpConstants.SSH_FILEXFER_TYPE_REGULAR); - } - - @Override - public Channel getChannel() { - return getClientChannel(); - } - - @Override - public <E extends SftpClientExtension> E getExtension(Class<? extends E> extensionType) { - Object instance = getExtension(BuiltinSftpClientExtensions.fromType(extensionType)); - if (instance == null) { - return null; - } else { - return extensionType.cast(instance); - } - } - - @Override - public SftpClientExtension getExtension(String extensionName) { - return getExtension(BuiltinSftpClientExtensions.fromName(extensionName)); - } - - protected SftpClientExtension getExtension(SftpClientExtensionFactory factory) { - if (factory == null) { - return null; - } - - Map<String, byte[]> extensions = getServerExtensions(); - Map<String, Object> parsed = getParsedServerExtensions(extensions); - return factory.create(this, this, extensions, parsed); - } - - protected Map<String, Object> getParsedServerExtensions() { - return getParsedServerExtensions(getServerExtensions()); - } - - protected Map<String, Object> getParsedServerExtensions(Map<String, byte[]> extensions) { - Map<String, Object> parsed = parsedExtensionsHolder.get(); - if (parsed == null) { - parsed = ParserUtils.parse(extensions); - if (parsed == null) { - parsed = Collections.emptyMap(); - } - parsedExtensionsHolder.set(parsed); - } - - return parsed; - } - - protected String getReferencedName(Buffer buf) { - Charset cs = getNameDecodingCharset(); - return buf.getString(cs); - } - - /** - * Sends the specified command, waits for the response and then invokes {@link #checkResponseStatus(int, Buffer)} - * @param cmd The command to send - * @param request The request {@link Buffer} - * @throws IOException If failed to send, receive or check the returned status - * @see #send(int, Buffer) - * @see #receive(int) - * @see #checkResponseStatus(int, Buffer) - */ - protected void checkCommandStatus(int cmd, Buffer request) throws IOException { - int reqId = send(cmd, request); - Buffer response = receive(reqId); - checkResponseStatus(cmd, response); - } - - /** - * Checks if the incoming response is an {@code SSH_FXP_STATUS} one, - * and if so whether the substatus is {@code SSH_FX_OK}. - * - * @param cmd The sent command opcode - * @param buffer The received response {@link Buffer} - * @throws IOException If response does not carry a status or carries - * a bad status code - * @see #checkResponseStatus(int, int, int, String, String) - */ - protected void checkResponseStatus(int cmd, Buffer buffer) throws IOException { - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - checkResponseStatus(cmd, id, substatus, msg, lang); - } else { - //noinspection ThrowableResultOfMethodCallIgnored - handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_STATUS, id, type, length, buffer); - } - } - - /** - * @param cmd The sent command opcode - * @param id The request id - * @param substatus The sub-status value - * @param msg The message - * @param lang The language - * @throws IOException if the sub-status is not {@code SSH_FX_OK} - * @see #throwStatusException(int, int, int, String, String) - */ - protected void checkResponseStatus(int cmd, int id, int substatus, String msg, String lang) throws IOException { - if (log.isTraceEnabled()) { - log.trace("checkResponseStatus({})[id={}] cmd={} status={} lang={} msg={}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - SftpConstants.getStatusName(substatus), lang, msg); - } - - if (substatus != SftpConstants.SSH_FX_OK) { - throwStatusException(cmd, id, substatus, msg, lang); - } - } - - protected void throwStatusException(int cmd, int id, int substatus, String msg, String lang) throws IOException { - throw new SftpException(substatus, msg); - } - - /** - * @param cmd Command to be sent - * @param request The {@link Buffer} containing the request - * @return The received handle identifier - * @throws IOException If failed to send/receive or process the response - * @see #send(int, Buffer) - * @see #receive(int) - * @see #checkHandleResponse(int, Buffer) - */ - protected byte[] checkHandle(int cmd, Buffer request) throws IOException { - int reqId = send(cmd, request); - Buffer response = receive(reqId); - return checkHandleResponse(cmd, response); - } - - protected byte[] checkHandleResponse(int cmd, Buffer buffer) throws IOException { - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_HANDLE) { - return ValidateUtils.checkNotNullAndNotEmpty(buffer.getBytes(), "Null/empty handle in buffer", GenericUtils.EMPTY_OBJECT_ARRAY); - } - - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("checkHandleResponse({})[id={}] {} - status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - SftpConstants.getStatusName(substatus), lang, msg); - } - throwStatusException(cmd, id, substatus, msg, lang); - } - - return handleUnexpectedHandlePacket(cmd, id, type, length, buffer); - } - - protected byte[] handleUnexpectedHandlePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { - handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_HANDLE, id, type, length, buffer); - throw new SshException("No handling for unexpected handle packet id=" + id - + ", type=" + SftpConstants.getCommandMessageName(type) + ", length=" + length); - } - - /** - * @param cmd Command to be sent - * @param request Request {@link Buffer} - * @return The decoded response {@code Attributes} - * @throws IOException If failed to send/receive or process the response - * @see #send(int, Buffer) - * @see #receive(int) - * @see #checkAttributesResponse(int, Buffer) - */ - protected Attributes checkAttributes(int cmd, Buffer request) throws IOException { - int reqId = send(cmd, request); - Buffer response = receive(reqId); - return checkAttributesResponse(cmd, response); - } - - protected Attributes checkAttributesResponse(int cmd, Buffer buffer) throws IOException { - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_ATTRS) { - return readAttributes(buffer); - } - - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("checkAttributesResponse()[id={}] {} - status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - SftpConstants.getStatusName(substatus), lang, msg); - } - throwStatusException(cmd, id, substatus, msg, lang); - } - - return handleUnexpectedAttributesPacket(cmd, id, type, length, buffer); - } - - protected Attributes handleUnexpectedAttributesPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { - IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_ATTRS, id, type, length, buffer); - if (err != null) { - throw err; - } - - return null; - } - - /** - * @param cmd Command to be sent - * @param request The request {@link Buffer} - * @return The retrieved name - * @throws IOException If failed to send/receive or process the response - * @see #send(int, Buffer) - * @see #receive(int) - * @see #checkOneNameResponse(int, Buffer) - */ - protected String checkOneName(int cmd, Buffer request) throws IOException { - int reqId = send(cmd, request); - Buffer response = receive(reqId); - return checkOneNameResponse(cmd, response); - } - - protected String checkOneNameResponse(int cmd, Buffer buffer) throws IOException { - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_NAME) { - int len = buffer.getInt(); - if (len != 1) { - throw new SshException("SFTP error: received " + len + " names instead of 1"); - } - String name = getReferencedName(buffer); - String longName = null; - int version = getVersion(); - if (version == SftpConstants.SFTP_V3) { - longName = getReferencedName(buffer); - } - - Attributes attrs = readAttributes(buffer); - Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version); - // TODO decide what to do if not-null and not TRUE - if (log.isTraceEnabled()) { - log.trace("checkOneNameResponse({})[id={}] {} ({})[{}] eol={}: {}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - name, longName, indicator, attrs); - } - return name; - } - - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("checkOneNameResponse({})[id={}] {} status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - SftpConstants.getStatusName(substatus), lang, msg); - } - - throwStatusException(cmd, id, substatus, msg, lang); - } - - return handleUnknownOneNamePacket(cmd, id, type, length, buffer); - } - - protected String handleUnknownOneNamePacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { - IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer); - if (err != null) { - throw err; - } - - return null; - } - - protected Attributes readAttributes(Buffer buffer) throws IOException { - Attributes attrs = new Attributes(); - int flags = buffer.getInt(); - int version = getVersion(); - if (version == SftpConstants.SFTP_V3) { - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { - attrs.setSize(buffer.getLong()); - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) { - attrs.owner(buffer.getInt(), buffer.getInt()); - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { - int perms = buffer.getInt(); - attrs.setPermissions(perms); - attrs.setType(SftpHelper.permissionsToFileType(perms)); - } - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) { - attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags)); - attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags)); - } - } else if (version >= SftpConstants.SFTP_V4) { - attrs.setType(buffer.getUByte()); - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { - attrs.setSize(buffer.getLong()); - } - - if ((version >= SftpConstants.SFTP_V6) && ((flags & SftpConstants.SSH_FILEXFER_ATTR_ALLOCATION_SIZE) != 0)) { - @SuppressWarnings("unused") - long allocSize = buffer.getLong(); // TODO handle allocation size - } - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { - attrs.setOwner(buffer.getString()); - attrs.setGroup(buffer.getString()); - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { - attrs.setPermissions(buffer.getInt()); - } - - // update the permissions according to the type - int perms = attrs.getPermissions(); - perms |= SftpHelper.fileTypeToPermission(attrs.getType()); - attrs.setPermissions(perms); - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { - attrs.setAccessTime(SftpHelper.readTime(buffer, version, flags)); - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) { - attrs.setCreateTime(SftpHelper.readTime(buffer, version, flags)); - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { - attrs.setModifyTime(SftpHelper.readTime(buffer, version, flags)); - } - if ((version >= SftpConstants.SFTP_V6) && (flags & SftpConstants.SSH_FILEXFER_ATTR_CTIME) != 0) { - @SuppressWarnings("unused") - FileTime attrsChangedTime = SftpHelper.readTime(buffer, version, flags); // TODO the last time the file attributes were changed - } - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) { - attrs.setAcl(SftpHelper.readACLs(buffer, version)); - } - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_BITS) != 0) { - @SuppressWarnings("unused") - int bits = buffer.getInt(); - @SuppressWarnings("unused") - int valid = 0xffffffff; - if (version >= SftpConstants.SFTP_V6) { - valid = buffer.getInt(); - } - // TODO: handle attrib bits - } - - if (version >= SftpConstants.SFTP_V6) { - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_TEXT_HINT) != 0) { - @SuppressWarnings("unused") - boolean text = buffer.getBoolean(); // TODO: handle text - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_MIME_TYPE) != 0) { - @SuppressWarnings("unused") - String mimeType = buffer.getString(); // TODO: handle mime-type - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_LINK_COUNT) != 0) { - @SuppressWarnings("unused") - int nlink = buffer.getInt(); // TODO: handle link-count - } - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_UNTRANSLATED_NAME) != 0) { - @SuppressWarnings("unused") - String untranslated = getReferencedName(buffer); // TODO: handle untranslated-name - } - } - } else { - throw new IllegalStateException("readAttributes - unsupported version: " + version); - } - - if ((flags & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) { - attrs.setExtensions(SftpHelper.readExtensions(buffer)); - } - - return attrs; - } - - protected void writeAttributes(Buffer buffer, Attributes attributes) throws IOException { - int version = getVersion(); - int flagsMask = 0; - Collection<Attribute> flags = Objects.requireNonNull(attributes, "No attributes").getFlags(); - if (version == SftpConstants.SFTP_V3) { - for (Attribute a : flags) { - switch (a) { - case Size: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE; - break; - case UidGid: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_UIDGID; - break; - case Perms: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS; - break; - case AccessTime: - if (flags.contains(Attribute.ModifyTime)) { - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME; - } - break; - case ModifyTime: - if (flags.contains(Attribute.AccessTime)) { - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME; - } - break; - case Extensions: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED; - break; - default: // do nothing - } - } - buffer.putInt(flagsMask); - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { - buffer.putLong(attributes.getSize()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_UIDGID) != 0) { - buffer.putInt(attributes.getUserId()); - buffer.putInt(attributes.getGroupId()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { - buffer.putInt(attributes.getPermissions()); - } - - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACMODTIME) != 0) { - SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime()); - SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime()); - } - } else if (version >= SftpConstants.SFTP_V4) { - for (Attribute a : flags) { - switch (a) { - case Size: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_SIZE; - break; - case OwnerGroup: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP; - break; - case Perms: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS; - break; - case AccessTime: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME; - break; - case ModifyTime: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME; - break; - case CreateTime: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_CREATETIME; - break; - case Acl: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_ACL; - break; - case Extensions: - flagsMask |= SftpConstants.SSH_FILEXFER_ATTR_EXTENDED; - break; - default: // do nothing - } - } - buffer.putInt(flagsMask); - buffer.putByte((byte) attributes.getType()); - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_SIZE) != 0) { - buffer.putLong(attributes.getSize()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_OWNERGROUP) != 0) { - String owner = attributes.getOwner(); - buffer.putString(GenericUtils.isEmpty(owner) ? SftpUniversalOwnerAndGroup.Owner.getName() : owner); - - String group = attributes.getGroup(); - buffer.putString(GenericUtils.isEmpty(group) ? SftpUniversalOwnerAndGroup.Group.getName() : group); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_PERMISSIONS) != 0) { - buffer.putInt(attributes.getPermissions()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACCESSTIME) != 0) { - SftpHelper.writeTime(buffer, version, flagsMask, attributes.getAccessTime()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_CREATETIME) != 0) { - SftpHelper.writeTime(buffer, version, flagsMask, attributes.getCreateTime()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_MODIFYTIME) != 0) { - SftpHelper.writeTime(buffer, version, flagsMask, attributes.getModifyTime()); - } - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_ACL) != 0) { - SftpHelper.writeACLs(buffer, version, attributes.getAcl()); - } - - // TODO: for v6+ add CTIME (see https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-21) - } else { - throw new UnsupportedOperationException("writeAttributes(" + attributes + ") unsupported version: " + version); - } - - if ((flagsMask & SftpConstants.SSH_FILEXFER_ATTR_EXTENDED) != 0) { - SftpHelper.writeExtensions(buffer, attributes.getExtensions()); - } - } - - @Override - public CloseableHandle open(String path, Collection<OpenMode> options) throws IOException { - if (!isOpen()) { - throw new IOException("open(" + path + ")[" + options + "] client is closed"); - } - - /* - * Be consistent with FileChannel#open - if no mode specified then READ is assumed - */ - if (GenericUtils.isEmpty(options)) { - options = EnumSet.of(OpenMode.Read); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - int version = getVersion(); - int mode = 0; - if (version < SftpConstants.SFTP_V5) { - for (OpenMode m : options) { - switch (m) { - case Read: - mode |= SftpConstants.SSH_FXF_READ; - break; - case Write: - mode |= SftpConstants.SSH_FXF_WRITE; - break; - case Append: - mode |= SftpConstants.SSH_FXF_APPEND; - break; - case Create: - mode |= SftpConstants.SSH_FXF_CREAT; - break; - case Truncate: - mode |= SftpConstants.SSH_FXF_TRUNC; - break; - case Exclusive: - mode |= SftpConstants.SSH_FXF_EXCL; - break; - default: // do nothing - } - } - } else { - int access = 0; - if (options.contains(OpenMode.Read)) { - access |= SftpConstants.ACE4_READ_DATA | SftpConstants.ACE4_READ_ATTRIBUTES; - } - if (options.contains(OpenMode.Write)) { - access |= SftpConstants.ACE4_WRITE_DATA | SftpConstants.ACE4_WRITE_ATTRIBUTES; - } - if (options.contains(OpenMode.Append)) { - access |= SftpConstants.ACE4_APPEND_DATA; - } - buffer.putInt(access); - - if (options.contains(OpenMode.Create) && options.contains(OpenMode.Exclusive)) { - mode |= SftpConstants.SSH_FXF_CREATE_NEW; - } else if (options.contains(OpenMode.Create) && options.contains(OpenMode.Truncate)) { - mode |= SftpConstants.SSH_FXF_CREATE_TRUNCATE; - } else if (options.contains(OpenMode.Create)) { - mode |= SftpConstants.SSH_FXF_OPEN_OR_CREATE; - } else if (options.contains(OpenMode.Truncate)) { - mode |= SftpConstants.SSH_FXF_TRUNCATE_EXISTING; - } else { - mode |= SftpConstants.SSH_FXF_OPEN_EXISTING; - } - } - buffer.putInt(mode); - writeAttributes(buffer, fileOpenAttributes); - - CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPEN, buffer)); - if (log.isTraceEnabled()) { - log.trace("open({})[{}] options={}: {}", getClientSession(), path, options, handle); - } - return handle; - } - - @Override - public void close(Handle handle) throws IOException { - if (!isOpen()) { - throw new IOException("close(" + handle + ") client is closed"); - } - - if (log.isTraceEnabled()) { - log.trace("close({}) {}", getClientSession(), handle); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false); - buffer.putBytes(id); - checkCommandStatus(SftpConstants.SSH_FXP_CLOSE, buffer); - } - - @Override - public void remove(String path) throws IOException { - if (!isOpen()) { - throw new IOException("remove(" + path + ") client is closed"); - } - - if (log.isDebugEnabled()) { - log.debug("remove({}) {}", getClientSession(), path); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - checkCommandStatus(SftpConstants.SSH_FXP_REMOVE, buffer); - } - - @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"); - } - - if (log.isDebugEnabled()) { - log.debug("rename({}) {} => {}", getClientSession(), oldPath, newPath); - } - - Buffer buffer = new ByteArrayBuffer(oldPath.length() + newPath.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(oldPath); - buffer.putString(newPath); - - int numOptions = GenericUtils.size(options); - int version = getVersion(); - if (version >= SftpConstants.SFTP_V5) { - int opts = 0; - if (numOptions > 0) { - for (CopyMode opt : options) { - switch (opt) { - case Atomic: - opts |= SftpConstants.SSH_FXP_RENAME_ATOMIC; - break; - case Overwrite: - opts |= SftpConstants.SSH_FXP_RENAME_OVERWRITE; - break; - default: // do nothing - } - } - } - buffer.putInt(opts); - } else if (numOptions > 0) { - throw new UnsupportedOperationException("rename(" + oldPath + " => " + newPath + ")" - + " - copy options can not be used with this SFTP version: " + options); - } - checkCommandStatus(SftpConstants.SSH_FXP_RENAME, buffer); - } - - @Override - public int read(Handle handle, long fileOffset, byte[] dst, int dstOffset, int len, AtomicReference<Boolean> eofSignalled) throws IOException { - if (eofSignalled != null) { - eofSignalled.set(null); - } - - if (!isOpen()) { - throw new IOException("read(" + handle + "/" + fileOffset + ")[" + dstOffset + "/" + len + "] client is closed"); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* some extra fields */, false); - buffer.putBytes(id); - buffer.putLong(fileOffset); - buffer.putInt(len); - return checkData(SftpConstants.SSH_FXP_READ, buffer, dstOffset, dst, eofSignalled); - } - - protected int checkData(int cmd, Buffer request, int dstOffset, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException { - if (eofSignalled != null) { - eofSignalled.set(null); - } - int reqId = send(cmd, request); - Buffer response = receive(reqId); - return checkDataResponse(cmd, response, dstOffset, dst, eofSignalled); - } - - protected int checkDataResponse(int cmd, Buffer buffer, int dstoff, byte[] dst, AtomicReference<Boolean> eofSignalled) throws IOException { - if (eofSignalled != null) { - eofSignalled.set(null); - } - - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_DATA) { - int len = buffer.getInt(); - buffer.getRawBytes(dst, dstoff, len); - Boolean indicator = SftpHelper.getEndOfFileIndicatorValue(buffer, getVersion()); - if (log.isTraceEnabled()) { - log.trace("checkDataResponse({}][id={}] {} offset={}, len={}, EOF={}", - getClientChannel(), SftpConstants.getCommandMessageName(cmd), - id, dstoff, len, indicator); - } - if (eofSignalled != null) { - eofSignalled.set(indicator); - } - - return len; - } - - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("checkDataResponse({})[id={}] {} status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getCommandMessageName(cmd), - SftpConstants.getStatusName(substatus), lang, msg); - } - - if (substatus == SftpConstants.SSH_FX_EOF) { - return -1; - } - - throwStatusException(cmd, id, substatus, msg, lang); - } - - return handleUnknownDataPacket(cmd, id, type, length, buffer); - } - - protected int handleUnknownDataPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { - IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_DATA, id, type, length, buffer); - if (err != null) { - throw err; - } - - return 0; - } - - @Override - public void write(Handle handle, long fileOffset, byte[] src, int srcOffset, int len) throws IOException { - // do some bounds checking first - if ((fileOffset < 0) || (srcOffset < 0) || (len < 0)) { - throw new IllegalArgumentException("write(" + handle + ") please ensure all parameters " - + " are non-negative values: file-offset=" + fileOffset - + ", src-offset=" + srcOffset + ", len=" + len); - } - if ((srcOffset + len) > src.length) { - throw new IllegalArgumentException("write(" + handle + ")" - + " cannot read bytes " + srcOffset + " to " + (srcOffset + len) - + " when array is only of length " + src.length); - } - if (!isOpen()) { - throw new IOException("write(" + handle + "/" + fileOffset + ")[" + srcOffset + "/" + len + "] client is closed"); - } - - if (log.isTraceEnabled()) { - log.trace("write({}) handle={}, file-offset={}, buf-offset={}, len={}", - getClientChannel(), handle, fileOffset, srcOffset, len); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + len + Long.SIZE /* some extra fields */, false); - buffer.putBytes(id); - buffer.putLong(fileOffset); - buffer.putBytes(src, srcOffset, len); - checkCommandStatus(SftpConstants.SSH_FXP_WRITE, buffer); - } - - @Override - public void mkdir(String path) throws IOException { - if (!isOpen()) { - throw new IOException("mkdir(" + path + ") client is closed"); - } - - if (log.isDebugEnabled()) { - log.debug("mkdir({}) {}", getClientSession(), path); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - buffer.putInt(0); - - int version = getVersion(); - if (version != SftpConstants.SFTP_V3) { - buffer.putByte((byte) 0); - } - - checkCommandStatus(SftpConstants.SSH_FXP_MKDIR, buffer); - } - - @Override - public void rmdir(String path) throws IOException { - if (!isOpen()) { - throw new IOException("rmdir(" + path + ") client is closed"); - } - - if (log.isDebugEnabled()) { - log.debug("rmdir({}) {}", getClientSession(), path); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - checkCommandStatus(SftpConstants.SSH_FXP_RMDIR, buffer); - } - - @Override - public CloseableHandle openDir(String path) throws IOException { - if (!isOpen()) { - throw new IOException("openDir(" + path + ") client is closed"); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - - CloseableHandle handle = new DefaultCloseableHandle(this, path, checkHandle(SftpConstants.SSH_FXP_OPENDIR, buffer)); - if (log.isTraceEnabled()) { - log.trace("openDir({})[{}}: {}", getClientSession(), path, handle); - } - - return handle; - } - - @Override - public List<DirEntry> readDir(Handle handle, AtomicReference<Boolean> eolIndicator) throws IOException { - if (eolIndicator != null) { - eolIndicator.set(null); // assume unknown information - } - if (!isOpen()) { - throw new IOException("readDir(" + handle + ") client is closed"); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* some extra fields */, false); - buffer.putBytes(id); - - int cmdId = send(SftpConstants.SSH_FXP_READDIR, buffer); - Buffer response = receive(cmdId); - return checkDirResponse(SftpConstants.SSH_FXP_READDIR, response, eolIndicator); - } - - protected List<DirEntry> checkDirResponse(int cmd, Buffer buffer, AtomicReference<Boolean> eolIndicator) throws IOException { - if (eolIndicator != null) { - eolIndicator.set(null); // assume unknown - } - - int length = buffer.getInt(); - int type = buffer.getUByte(); - int id = buffer.getInt(); - if (type == SftpConstants.SSH_FXP_NAME) { - int len = buffer.getInt(); - int version = getVersion(); - ClientChannel channel = getClientChannel(); - if (log.isDebugEnabled()) { - log.debug("checkDirResponse({}}[id={}] reading {} entries", channel, id, len); - } - - List<DirEntry> entries = new ArrayList<>(len); - for (int i = 0; i < len; i++) { - String name = getReferencedName(buffer); - String longName = (version == SftpConstants.SFTP_V3) ? getReferencedName(buffer) : null; - Attributes attrs = readAttributes(buffer); - if (log.isTraceEnabled()) { - log.trace("checkDirResponse({})[id={}][{}] ({})[{}]: {}", - channel, id, i, name, longName, attrs); - } - - entries.add(new DirEntry(name, longName, attrs)); - } - - Boolean indicator = SftpHelper.getEndOfListIndicatorValue(buffer, version); - if (eolIndicator != null) { - eolIndicator.set(indicator); - } - - if (log.isDebugEnabled()) { - log.debug("checkDirResponse({}}[id={}] read count={}, eol={}", channel, entries.size(), indicator); - } - return entries; - } - - if (type == SftpConstants.SSH_FXP_STATUS) { - int substatus = buffer.getInt(); - String msg = buffer.getString(); - String lang = buffer.getString(); - if (log.isTraceEnabled()) { - log.trace("checkDirResponse({})[id={}] - status: {} [{}] {}", - getClientChannel(), id, SftpConstants.getStatusName(substatus), lang, msg); - } - - if (substatus == SftpConstants.SSH_FX_EOF) { - return null; - } - - throwStatusException(cmd, id, substatus, msg, lang); - } - - return handleUnknownDirListingPacket(cmd, id, type, length, buffer); - } - - protected List<DirEntry> handleUnknownDirListingPacket(int cmd, int id, int type, int length, Buffer buffer) throws IOException { - IOException err = handleUnexpectedPacket(cmd, SftpConstants.SSH_FXP_NAME, id, type, length, buffer); - if (err != null) { - throw err; - } - return Collections.emptyList(); - } - - protected IOException handleUnexpectedPacket(int cmd, int expected, int id, int type, int length, Buffer buffer) throws IOException { - throw new SshException("Unexpected SFTP packet received while awaiting " + SftpConstants.getCommandMessageName(expected) - + " response to " + SftpConstants.getCommandMessageName(cmd) - + ": type=" + SftpConstants.getCommandMessageName(type) + ", id=" + id + ", length=" + length); - } - - @Override - public String canonicalPath(String path) throws IOException { - if (!isOpen()) { - throw new IOException("canonicalPath(" + path + ") client is closed"); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); - buffer.putString(path); - return checkOneName(SftpConstants.SSH_FXP_REALPATH, buffer); - } - - @Override - public Attributes stat(String path) throws IOException { - if (!isOpen()) { - throw new IOException("stat(" + path + ") client is closed"); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); - buffer.putString(path); - - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); - } - - return checkAttributes(SftpConstants.SSH_FXP_STAT, buffer); - } - - @Override - public Attributes lstat(String path) throws IOException { - if (!isOpen()) { - throw new IOException("lstat(" + path + ") client is closed"); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE, false); - buffer.putString(path); - - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); - } - - return checkAttributes(SftpConstants.SSH_FXP_LSTAT, buffer); - } - - @Override - public Attributes stat(Handle handle) throws IOException { - if (!isOpen()) { - throw new IOException("stat(" + handle + ") client is closed"); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Byte.SIZE /* a bit extra */, false); - buffer.putBytes(id); - - int version = getVersion(); - if (version >= SftpConstants.SFTP_V4) { - buffer.putInt(SftpConstants.SSH_FILEXFER_ATTR_ALL); - } - - return checkAttributes(SftpConstants.SSH_FXP_FSTAT, buffer); - } - - @Override - public void setStat(String path, Attributes attributes) throws IOException { - if (!isOpen()) { - throw new IOException("setStat(" + path + ")[" + attributes + "] client is closed"); - } - - if (log.isDebugEnabled()) { - log.debug("setStat({})[{}]: {}", getClientSession(), path, attributes); - } - - Buffer buffer = new ByteArrayBuffer(); - buffer.putString(path); - writeAttributes(buffer, attributes); - checkCommandStatus(SftpConstants.SSH_FXP_SETSTAT, buffer); - } - - @Override - public void setStat(Handle handle, Attributes attributes) throws IOException { - if (!isOpen()) { - throw new IOException("setStat(" + handle + ")[" + attributes + "] client is closed"); - } - - if (log.isDebugEnabled()) { - log.debug("setStat({})[{}]: {}", getClientSession(), handle, attributes); - } - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + (2 * Long.SIZE) /* some extras */, false); - buffer.putBytes(id); - writeAttributes(buffer, attributes); - checkCommandStatus(SftpConstants.SSH_FXP_FSETSTAT, buffer); - } - - @Override - public String readLink(String path) throws IOException { - if (!isOpen()) { - throw new IOException("readLink(" + path + ") client is closed"); - } - - Buffer buffer = new ByteArrayBuffer(path.length() + Long.SIZE /* some extra fields */, false); - buffer.putString(path); - return checkOneName(SftpConstants.SSH_FXP_READLINK, buffer); - } - - @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"); - } - - if (log.isDebugEnabled()) { - log.debug("link({})[symbolic={}] {} => {}", getClientSession(), symbolic, linkPath, targetPath); - } - - Buffer buffer = new ByteArrayBuffer(linkPath.length() + targetPath.length() + Long.SIZE /* some extra fields */, false); - int version = getVersion(); - if (version < SftpConstants.SFTP_V6) { - if (!symbolic) { - throw new UnsupportedOperationException("Hard links are not supported in sftp v" + version); - } - buffer.putString(targetPath); - buffer.putString(linkPath); - checkCommandStatus(SftpConstants.SSH_FXP_SYMLINK, buffer); - } else { - buffer.putString(targetPath); - buffer.putString(linkPath); - buffer.putBoolean(symbolic); - checkCommandStatus(SftpConstants.SSH_FXP_LINK, buffer); - } - } - - @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"); - } - - if (log.isDebugEnabled()) { - log.debug("lock({})[{}] offset={}, length={}, mask=0x{}", - getClientSession(), handle, offset, length, Integer.toHexString(mask)); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false); - buffer.putBytes(id); - buffer.putLong(offset); - buffer.putLong(length); - buffer.putInt(mask); - checkCommandStatus(SftpConstants.SSH_FXP_BLOCK, buffer); - } - - @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"); - } - - if (log.isDebugEnabled()) { - log.debug("unlock({})[{}] offset={}, length={}", getClientSession(), handle, offset, length); - } - - byte[] id = Objects.requireNonNull(handle, "No handle").getIdentifier(); - Buffer buffer = new ByteArrayBuffer(id.length + Long.SIZE /* a bit extra */, false); - buffer.putBytes(id); - buffer.putLong(offset); - buffer.putLong(length); - checkCommandStatus(SftpConstants.SSH_FXP_UNBLOCK, buffer); - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java deleted file mode 100644 index 71b42a3..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/AbstractSftpFileAttributeView.java +++ /dev/null @@ -1,88 +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.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.attribute.FileAttributeView; -import java.util.Objects; - -import org.apache.sshd.common.subsystem.sftp.SftpConstants; -import org.apache.sshd.common.subsystem.sftp.SftpException; -import org.apache.sshd.common.util.logging.AbstractLoggingBean; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public abstract class AbstractSftpFileAttributeView extends AbstractLoggingBean implements FileAttributeView { - protected final SftpFileSystemProvider provider; - protected final Path path; - protected final LinkOption[] options; - - protected AbstractSftpFileAttributeView(SftpFileSystemProvider provider, Path path, LinkOption... options) { - this.provider = Objects.requireNonNull(provider, "No file system provider instance"); - this.path = Objects.requireNonNull(path, "No path"); - this.options = options; - } - - @Override - public String name() { - return "view"; - } - - /** - * @return The underlying {@link SftpFileSystemProvider} used to - * provide the view functionality - */ - public final SftpFileSystemProvider provider() { - return provider; - } - - /** - * @return The referenced view {@link Path} - */ - public final Path getPath() { - return path; - } - - protected SftpClient.Attributes readRemoteAttributes() throws IOException { - return provider.readRemoteAttributes(provider.toSftpPath(path), options); - } - - protected void writeRemoteAttributes(SftpClient.Attributes attrs) throws IOException { - SftpPath p = provider.toSftpPath(path); - SftpFileSystem fs = p.getFileSystem(); - try (SftpClient client = fs.getClient()) { - try { - if (log.isDebugEnabled()) { - log.debug("writeRemoteAttributes({})[{}]: {}", fs, p, attrs); - } - client.setStat(p.toString(), attrs); - } catch (SftpException e) { - if (e.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) { - throw new NoSuchFileException(p.toString()); - } - throw e; - } - } - } -} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/15428746/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.java deleted file mode 100644 index 67ad906..0000000 --- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/DefaultCloseableHandle.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.util.concurrent.atomic.AtomicBoolean; - -import org.apache.sshd.client.subsystem.sftp.SftpClient.CloseableHandle; -import org.apache.sshd.common.util.ValidateUtils; - -/** - * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> - */ -public class DefaultCloseableHandle extends CloseableHandle { - private final AtomicBoolean open = new AtomicBoolean(true); - private final SftpClient client; - - public DefaultCloseableHandle(SftpClient client, String path, byte[] id) { - super(path, id); - this.client = ValidateUtils.checkNotNull(client, "No client for path=%s", path); - } - - public final SftpClient getSftpClient() { - return client; - } - - @Override - public boolean isOpen() { - return open.get(); - } - - @Override - public void close() throws IOException { - if (open.getAndSet(false)) { - client.close(this); - } - } - - @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS] - public int hashCode() { - return super.hashCode(); - } - - @Override // to avoid Findbugs[EQ_DOESNT_OVERRIDE_EQUALS] - public boolean equals(Object obj) { - return super.equals(obj); - } -}
