This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push: new 28b0aaa [SSHD-1089] Added wrappers for one-time single session usage of SFTP/SCP clients 28b0aaa is described below commit 28b0aaa14844c4f2e9803548901430e93dc93286 Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Sat Sep 26 10:43:43 2020 +0300 [SSHD-1089] Added wrappers for one-time single session usage of SFTP/SCP clients --- CHANGES.md | 1 + docs/scp.md | 26 +- docs/sftp.md | 29 +- .../apache/sshd/common/session/SessionContext.java | 4 +- .../sshd/common/util/EventListenerUtils.java | 10 +- .../org/apache/sshd/common/util/ProxyUtils.java | 51 +++ .../AutoCloseableDelegateInvocationHandler.java | 129 +++++++ .../NioChannelDelegateInvocationHandler.java | 95 +++++ .../apache/sshd/util/test/JUnitTestSupport.java | 6 +- .../java/org/apache/sshd/KeyReExchangeTest.java | 5 +- .../sshd/common/forward/PortForwardingTest.java | 5 +- .../apache/sshd/scp/client/CloseableScpClient.java | 12 +- .../sshd/scp/client/SimpleScpClientImpl.java | 25 +- .../sshd/scp/client/AbstractScpTestSupport.java | 18 + .../java/org/apache/sshd/scp/client/ScpTest.java | 80 ++--- .../sshd/sftp/client/FullAccessSftpClient.java | 20 +- .../org/apache/sshd/sftp/client/SftpClient.java | 10 + .../sshd/sftp/client/impl/AbstractSftpClient.java | 5 +- .../sftp/client/impl/SimpleSftpClientImpl.java | 60 +--- .../sftp/client/AbstractSftpClientTestSupport.java | 22 ++ .../java/org/apache/sshd/sftp/client/SftpTest.java | 396 ++++++++++----------- .../extensions/UnsupportedExtensionTest.java | 8 +- .../helpers/AbstractCheckFileExtensionTest.java | 4 +- .../helpers/AbstractMD5HashExtensionTest.java | 4 +- .../helpers/CopyDataExtensionImplTest.java | 4 +- .../helpers/CopyFileExtensionImplTest.java | 4 +- .../helpers/SpaceAvailableExtensionImplTest.java | 4 +- .../openssh/helpers/OpenSSHExtensionsTest.java | 7 +- .../sftp/client/fs/SftpDirectoryScannersTest.java | 4 +- .../client/impl/SftpRemotePathChannelTest.java | 46 ++- 30 files changed, 682 insertions(+), 412 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1c35b7c..c9d2864 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ or `-key-file` command line option. * [SSHD-1077](https://issues.apache.org/jira/browse/SSHD-1077) Added command line option to request specific SFTP version in `SftpCommandMain` * [SSHD-1079](https://issues.apache.org/jira/browse/SSHD-1079) Experimental async mode on the local port forwarder * [SSHD-1086](https://issues.apache.org/jira/browse/SSHD-1086) Added SFTP aware directory scanning helper classes +* [SSHD-1089](https://issues.apache.org/jira/browse/SSHD-1089) Added wrappers for one-time single session usage of SFTP/SCP clients ## Behavioral changes and enhancements diff --git a/docs/scp.md b/docs/scp.md index 499241c..b3f131c 100644 --- a/docs/scp.md +++ b/docs/scp.md @@ -44,14 +44,32 @@ In order to obtain an `ScpClient` one needs to use an `ScpClientCreator`: ```java -ClientSession session = ... obtain an instance ... -ScpClientCreator creator = ... obtain an instance ... -ScpClient client = creator.createScpClient(session); - +try (ClientSession session = ... obtain an instance ...) { + ScpClientCreator creator = ... obtain an instance ... + ScpClient client = creator.createScpClient(session); + ... use client ... +} ``` A default `ScpClientCreator` instance is provided as part of the module - see `ScpClientCreator.instance()` +If the intended use of the client instance is "one-shot" - i.e., the client session should be closed when the +SCP client instance is closed, then it is possible to obtain a special wrapper that implements this functionality: + +```java +// The underlying session will also be closed when the client is +try (CloseableScpClient client = createScpClient(...)) { + ... use client ... +} + +CloseableScpClient createScpClient(...) { + ClientSession session = ... obtain an instance ...; + ScpClientCreator creator = ... obtain an instance ... + ScpClient client = creator.createScpClient(session); + return CloseableScpClient.singleSessionInstance(client); +} +``` + The `ScpClientCreator` can also be used to attach a default `ScpTransferEventListener` that will be automatically add to **all** created SCP client instances through that creator - unless specifically overridden: diff --git a/docs/sftp.md b/docs/sftp.md index 7a1b15a..3fd7cc9 100644 --- a/docs/sftp.md +++ b/docs/sftp.md @@ -143,14 +143,37 @@ In order to obtain an `SftpClient` instance one needs to use an `SftpClientFacto ```java - ClientSession session = ...obtain session... - SftpClientFactory factory = ...obtain factory... - SftpClient client = factory.createSftpClient(session); + try (ClientSession session = ...obtain session...) { + SftpClientFactory factory = ...obtain factory... + try (SftpClient client = factory.createSftpClient(session)) { + ... use the SFTP client... + } + + // NOTE: session is still alive here... + } ``` A default client factory implementations is provided in the module - see `SftpClientFactory.instance()` +If the intended use of the client instance is "one-shot" - i.e., the client session should be closed when the +SFTP client instance is closed, then it is possible to obtain a special wrapper that implements this functionality: + +```java + +// The underlying session will also be closed when the client is +try (SftpClient client = createSftpClient(....)) { + ... use the SFTP client... +} + +SftpClient createSftpClient(...) { + ClientSession session = ...obtain session... + SftpClientFactory factory = ...obtain factory... + SftpClient client = factory.createSftpClient(session); + return client.singleSessionInstance(); +} +``` + ### Using a custom `SftpClientFactory` The code creates `SftpClient`-s and `SftpFileSystem`-s using a default built-in `SftpClientFactory` instance (see diff --git a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java index 4bc87f6..622c5db 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/session/SessionContext.java @@ -46,14 +46,14 @@ public interface SessionContext Closeable { /** * Default prefix expected for the client / server identification string - * + * * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2</A> */ String DEFAULT_SSH_VERSION_PREFIX = "SSH-2.0-"; /** * Backward compatible special prefix - * + * * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-5">RFC 4253 - section 5</A> */ String FALLBACK_SSH_VERSION_PREFIX = "SSH-1.99-"; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java index f66f3c6..ffc719f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/EventListenerUtils.java @@ -189,13 +189,10 @@ public final class EventListenerUtils { * @see #proxyWrapper(Class, ClassLoader, Iterable) */ public static <T extends SshdEventListener> T proxyWrapper( - Class<T> listenerType, ClassLoader loader, final Iterable<? extends T> listeners) { - Objects.requireNonNull(listenerType, "No listener type specified"); - ValidateUtils.checkTrue(listenerType.isInterface(), "Target proxy is not an interface: %s", - listenerType.getSimpleName()); + Class<T> listenerType, ClassLoader loader, Iterable<? extends T> listeners) { Objects.requireNonNull(listeners, "No listeners container provided"); - Object wrapper = Proxy.newProxyInstance(loader, new Class<?>[] { listenerType }, (proxy, method, args) -> { + return ProxyUtils.newProxyInstance(loader, listenerType, (proxy, method, args) -> { Throwable err = null; for (T l : listeners) { try { @@ -207,11 +204,10 @@ public final class EventListenerUtils { } if (err != null) { - throw err; + throw ProxyUtils.unwrapInvocationThrowable(err); } return null; // we assume always void return value... }); - return listenerType.cast(wrapper); } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java new file mode 100644 index 0000000..864c5b6 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/ProxyUtils.java @@ -0,0 +1,51 @@ +/* + * 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.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public final class ProxyUtils { + private ProxyUtils() { + throw new UnsupportedOperationException("No instance"); + } + + public static <T> T newProxyInstance(Class<T> type, InvocationHandler handler) { + return newProxyInstance(type.getClassLoader(), type, handler); + } + + public static <T> T newProxyInstance(ClassLoader cl, Class<T> type, InvocationHandler handler) { + Class<?>[] interfaces = { type }; + Object wrapper = Proxy.newProxyInstance(cl, interfaces, handler); + return type.cast(wrapper); + } + + public static Throwable unwrapInvocationThrowable(Throwable t) { + if (t instanceof InvocationTargetException) { + return unwrapInvocationThrowable(((InvocationTargetException) t).getTargetException()); + } else { + return t; + } + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java new file mode 100644 index 0000000..e6914d5 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/AutoCloseableDelegateInvocationHandler.java @@ -0,0 +1,129 @@ +/* + * 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.util.closeable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Objects; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ProxyUtils; +import org.apache.sshd.common.util.logging.LoggingUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps a target instance and an {@link AutoCloseable} delegate into a proxy instance that closes both when wrapper + * {@link AutoCloseable#close() close} method called. + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class AutoCloseableDelegateInvocationHandler implements InvocationHandler { + private final Object proxyTarget; + private final AutoCloseable delegate; + // Order is important - we want to close the proxy before the delegate + private final Object[] closers; + + public AutoCloseableDelegateInvocationHandler(Object proxyTarget, AutoCloseable delegate) { + this.proxyTarget = Objects.requireNonNull(proxyTarget, "No proxy target to wrap"); + this.delegate = Objects.requireNonNull(delegate, "No delegate to auto-close"); + this.closers = new Object[] { proxyTarget, delegate }; + } + + public Object getProxyTarget() { + return proxyTarget; + } + + public AutoCloseable getAutoCloseableDelegate() { + return delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // If not invoking "close" then propagate to target as-is + if (!isCloseMethodInvocation(method, args)) { + Object target = getProxyTarget(); + try { + return method.invoke(target, args); + } catch (Throwable t) { + Class<?> targetType = target.getClass(); + Logger log = LoggerFactory.getLogger(targetType); + LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: {}", + targetType.getSimpleName(), method.getName(), t.getClass().getSimpleName(), t.getMessage(), t); + throw ProxyUtils.unwrapInvocationThrowable(t); + } + } + + Throwable err = null; + for (Object c : closers) { + if (!(c instanceof AutoCloseable)) { + continue; // OK if proxy target is not AutoCloseable + } + + try { + method.invoke(c, args); + } catch (Throwable t) { + Class<? extends Object> closerType = c.getClass(); + Logger log = LoggerFactory.getLogger(closerType); + LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: {}", + closerType.getSimpleName(), method.getName(), t.getClass().getSimpleName(), t.getMessage(), t); + err = GenericUtils.accumulateException(err, t); + } + } + + if (err != null) { + throw ProxyUtils.unwrapInvocationThrowable(err); + } + + return null; + } + + /** + * Wraps a target instance and an {@link AutoCloseable} delegate into a proxy instance that closes both when wrapper + * {@link AutoCloseable#close() close} method called. + * + * @param <T> The generic {@link AutoCloseable} wrapping interface + * @param proxyTarget The (never {@code null}) target instance - if not {@link AutoCloseable} then it's + * {@code close()} method will not be invoked (i.e., only the delegate) + * @param type The target wrapping interface + * @param delegate The (never {@code null}) delegate to close. <B>Note:</B> the delegate is closed <U>after</U> + * the target instance. + * @return The wrapping proxy + */ + public static <T extends AutoCloseable> T wrapDelegateCloseable( + Object proxyTarget, Class<T> type, AutoCloseable delegate) { + return ProxyUtils.newProxyInstance(type, new AutoCloseableDelegateInvocationHandler(proxyTarget, delegate)); + } + + public static boolean isCloseMethodInvocation(Method m, Object[] args) { + return isCloseMethod(m) && GenericUtils.isEmpty(args); + } + + public static boolean isCloseMethod(Method m) { + int mods = (m == null) ? 0 : m.getModifiers(); + return (m != null) + && "close".equals(m.getName()) + && Modifier.isPublic(mods) + && (!Modifier.isStatic(mods)) + && (void.class == m.getReturnType()) + && GenericUtils.isEmpty(m.getParameterTypes()); + } +} diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.java b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.java new file mode 100644 index 0000000..886eeb2 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/closeable/NioChannelDelegateInvocationHandler.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.common.util.closeable; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.channels.Channel; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ProxyUtils; +import org.apache.sshd.common.util.logging.LoggingUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps a target instance and a {@link Channel} delegate into a proxy instance that closes both when wrapper + * {@link AutoCloseable#close() close} method called. The {@link Channel#isOpen()} call is invoked only on the delegate + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class NioChannelDelegateInvocationHandler extends AutoCloseableDelegateInvocationHandler { + public NioChannelDelegateInvocationHandler(Object proxyTarget, Channel delegate) { + super(proxyTarget, delegate); + } + + public Channel getChannelDelegate() { + return Channel.class.cast(super.getAutoCloseableDelegate()); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (!isQueryOpenMethodInvocation(method, args)) { + return super.invoke(proxy, method, args); + } + + Channel channelDelegate = getChannelDelegate(); + try { + return method.invoke(channelDelegate, args); + } catch (Throwable t) { + Class<?> targetType = channelDelegate.getClass(); + Logger log = LoggerFactory.getLogger(targetType); + LoggingUtils.debug(log, "invoke({}#{}) failed ({}) to execute: {}", + targetType.getSimpleName(), method.getName(), t.getClass().getSimpleName(), t.getMessage(), t); + throw ProxyUtils.unwrapInvocationThrowable(t); + } + } + + /** + * Wraps a target instance and a {@link Channel} delegate into a proxy instance that closes both when wrapper + * {@link Channel#close() close} method called. The {@link Channel#isOpen()} call is invoked only on the delegate + * + * @param <T> The generic {@link Channel} wrapping interface + * @param proxyTarget The (never {@code null}) target instance - if not {@link AutoCloseable} then it's + * {@code close()} method will not be invoked (i.e., only the delegate) + * @param type The target wrapping interface + * @param delegate The (never {@code null}) delegate to use. <B>Note:</B> the delegate is closed <U>after</U> + * the target instance. + * @return The wrapping proxy + */ + public static <T extends Channel> T wrapDelegateChannel( + Object proxyTarget, Class<T> type, Channel delegate) { + return ProxyUtils.newProxyInstance(type, new NioChannelDelegateInvocationHandler(proxyTarget, delegate)); + } + + public static boolean isQueryOpenMethodInvocation(Method m, Object[] args) { + return isQueryOpenMethodInvocation(m) && GenericUtils.isEmpty(args); + } + + public static boolean isQueryOpenMethodInvocation(Method m) { + int mods = (m == null) ? 0 : m.getModifiers(); + return (m != null) + && "isOpen".equals(m.getName()) + && Modifier.isPublic(mods) + && (!Modifier.isStatic(mods)) + && (boolean.class == m.getReturnType()) + && GenericUtils.isEmpty(m.getParameterTypes()); + } +} 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 cd71bc2..a635fc9 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 @@ -608,7 +608,11 @@ public abstract class JUnitTestSupport extends Assert { long nanoSleep = sleepEnd - sleepStart; sleepTime = TimeUnit.NANOSECONDS.toMillis(nanoSleep); - timeout -= sleepTime; + if (sleepTime <= 0L) { + timeout -= 10L; + } else { + timeout -= sleepTime; + } } return false; diff --git a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java index 2724a8e..a536252 100644 --- a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java @@ -24,7 +24,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; -import java.lang.reflect.Proxy; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; @@ -52,6 +51,7 @@ import org.apache.sshd.common.kex.KeyExchangeFactory; import org.apache.sshd.common.session.Session; import org.apache.sshd.common.session.SessionListener; import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.ProxyUtils; import org.apache.sshd.common.util.io.NullOutputStream; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.core.CoreModuleProperties; @@ -156,7 +156,6 @@ public class KeyReExchangeTest extends BaseTestSupport { AtomicBoolean successfulInit = new AtomicBoolean(true); AtomicBoolean successfulNext = new AtomicBoolean(true); ClassLoader loader = getClass().getClassLoader(); - Class<?>[] interfaces = { KeyExchange.class }; for (KeyExchangeFactory factory : client.getKeyExchangeFactories()) { kexFactories.add(new KeyExchangeFactory() { @Override @@ -167,7 +166,7 @@ public class KeyReExchangeTest extends BaseTestSupport { @Override public KeyExchange createKeyExchange(Session s) throws Exception { KeyExchange proxiedInstance = factory.createKeyExchange(s); - return (KeyExchange) Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> { + return ProxyUtils.newProxyInstance(loader, KeyExchange.class, (proxy, method, args) -> { String name = method.getName(); if ("init".equals(name) && (!successfulInit.get())) { throw new UnsupportedOperationException("Intentionally failing 'init'"); diff --git a/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java b/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java index 1210e59..51bcb80 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/forward/PortForwardingTest.java @@ -24,7 +24,6 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -55,6 +54,7 @@ import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.MapEntryUtils.NavigableMapBuilder; +import org.apache.sshd.common.util.ProxyUtils; import org.apache.sshd.common.util.net.SshdSocketAddress; import org.apache.sshd.core.CoreModuleProperties; import org.apache.sshd.server.SshServer; @@ -180,7 +180,6 @@ public class PortForwardingTest extends BaseTestSupport { ForwarderFactory factory = Objects.requireNonNull(sshd.getForwarderFactory(), "No ForwarderFactory"); sshd.setForwarderFactory(new ForwarderFactory() { - private final Class<?>[] interfaces = { Forwarder.class }; private final Map<String, String> method2req = NavigableMapBuilder.<String, String> builder(String.CASE_INSENSITIVE_ORDER) .put("localPortForwardingRequested", TcpipForwardHandler.REQUEST) @@ -193,7 +192,7 @@ public class PortForwardingTest extends BaseTestSupport { ClassLoader cl = thread.getContextClassLoader(); Forwarder forwarder = factory.create(service); - return (Forwarder) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() { + return ProxyUtils.newProxyInstance(cl, Forwarder.class, new InvocationHandler() { private final org.slf4j.Logger log = LoggerFactory.getLogger(Forwarder.class); @SuppressWarnings("synthetic-access") diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java b/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java index 0171a88..68ddb1c 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java +++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java @@ -21,11 +21,21 @@ package org.apache.sshd.scp.client; import java.nio.channels.Channel; +import org.apache.sshd.common.util.closeable.NioChannelDelegateInvocationHandler; + /** * An {@link ScpClient} wrapper that also closes the underlying session when closed * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public interface CloseableScpClient extends ScpClient, Channel { - // Marker interface + /** + * @param client The (never {@code null}) {@link ScpClient} instance + * @return A {@link CloseableScpClient} wrapper that also closes the underlying {@link #getClientSession()} + * when closed + */ + static CloseableScpClient singleSessionInstance(ScpClient client) { + return NioChannelDelegateInvocationHandler.wrapDelegateChannel( + client, CloseableScpClient.class, client.getClientSession()); + } } diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java index afb1990..7b8e474 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java +++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java @@ -20,7 +20,6 @@ package org.apache.sshd.scp.client; import java.io.IOException; -import java.lang.reflect.Proxy; import java.net.SocketAddress; import java.security.KeyPair; import java.util.Objects; @@ -98,7 +97,7 @@ public class SimpleScpClientImpl extends AbstractLoggingBean implements SimpleSc try { ScpClientCreator creator = getScpClientCreator(); ScpClient client = creator.createScpClient(Objects.requireNonNull(session, "No client session")); - return createScpClient(session, client); + return CloseableScpClient.singleSessionInstance(client); } catch (Exception e) { log.warn("createScpClient({}) failed ({}) to create proxy: {}", session, e.getClass().getSimpleName(), e.getMessage()); @@ -114,28 +113,6 @@ public class SimpleScpClientImpl extends AbstractLoggingBean implements SimpleSc } } - protected CloseableScpClient createScpClient(ClientSession session, ScpClient client) throws IOException { - ClassLoader loader = CloseableScpClient.class.getClassLoader(); - Class<?>[] interfaces = { CloseableScpClient.class }; - return (CloseableScpClient) Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> { - String name = method.getName(); - try { - // The Channel implementation is provided by the session - if (("close".equals(name) || "isOpen".equals(name)) && GenericUtils.isEmpty(args)) { - return method.invoke(session, args); - } else { - return method.invoke(client, args); - } - } catch (Throwable t) { - if (log.isTraceEnabled()) { - log.trace("invoke(CloseableScpClient#{}) failed ({}) to execute: {}", - name, t.getClass().getSimpleName(), t.getMessage()); - } - throw t; - } - }); - } - @Override public boolean isOpen() { return true; diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java index abb0021..b5f6c87 100644 --- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java +++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java @@ -174,6 +174,24 @@ public abstract class AbstractScpTestSupport extends BaseTestSupport { return OUTPUT_DEBUG_MESSAGES ? DEBUG_LISTENER : ScpTransferEventListener.EMPTY; } + protected CloseableScpClient createCloseableScpClient() throws IOException { + return createCloseableScpClient(getCurrentTestName()); + } + + protected CloseableScpClient createCloseableScpClient(String username) throws IOException { + ClientSession session = createAuthenticatedClientSession(username); + try { + ScpClient client = createScpClient(session); + CloseableScpClient closer = CloseableScpClient.singleSessionInstance(client); + session = null; // avoid auto-close at finally clause + return closer; + } finally { + if (session != null) { + session.close(); + } + } + } + protected static ScpClient createScpClient(ClientSession session) { ScpClientCreator creator = ScpClientCreator.instance(); ScpTransferEventListener listener = getScpTransferEventListener(session); diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java index a07c097..214fe0a 100644 --- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java +++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/ScpTest.java @@ -120,8 +120,7 @@ public class ScpTest extends AbstractScpTestSupport { String[] remoteComps = GenericUtils.split(remotePath, '/'); Factory<? extends Random> factory = client.getRandomFactory(); Random rnd = factory.create(); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { StringBuilder sb = new StringBuilder(remotePath.length() + Long.SIZE); for (int i = 0; i < Math.max(Long.SIZE, remoteComps.length); i++) { if (sb.length() > 0) { @@ -175,8 +174,7 @@ public class ScpTest extends AbstractScpTestSupport { Path remoteFile = remoteDir.resolve(localFile.getFileName().toString()); String localPath = localFile.toString(); String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { scp.upload(localPath, remotePath); assertFileLength(remoteFile, data.length, DEFAULT_TIMEOUT); @@ -194,28 +192,28 @@ public class ScpTest extends AbstractScpTestSupport { @Test public void testScpUploadOverwrite() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); - String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL; + String data = getClass().getName() + "#" + getCurrentTestName() + IoUtils.EOL; - Path targetPath = detectTargetFolder(); - Path parentPath = targetPath.getParent(); - Path scpRoot = CommonTestSupportUtils.resolve(targetPath, - ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName()); - CommonTestSupportUtils.deleteRecursive(scpRoot); + Path targetPath = detectTargetFolder(); + Path parentPath = targetPath.getParent(); + Path scpRoot = CommonTestSupportUtils.resolve(targetPath, + ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName()); + CommonTestSupportUtils.deleteRecursive(scpRoot); - Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local")); - Path localFile = localDir.resolve("file.txt"); - CommonTestSupportUtils.writeFile(localFile, data); + Path localDir = assertHierarchyTargetFolderExists(scpRoot.resolve("local")); + Path localFile = localDir.resolve("file.txt"); + CommonTestSupportUtils.writeFile(localFile, data); - Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote")); - Path remoteFile = remoteDir.resolve(localFile.getFileName()); - CommonTestSupportUtils.writeFile(remoteFile, data + data); + Path remoteDir = assertHierarchyTargetFolderExists(scpRoot.resolve("remote")); + Path remoteFile = remoteDir.resolve(localFile.getFileName()); + CommonTestSupportUtils.writeFile(remoteFile, data + data); - String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile); + String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, remoteFile); + try (CloseableScpClient scp = createCloseableScpClient()) { scp.upload(localFile.toString(), remotePath); - assertFileLength(remoteFile, data.length(), DEFAULT_TIMEOUT); } + + assertFileLength(remoteFile, data.length(), DEFAULT_TIMEOUT); } @Test @@ -240,12 +238,11 @@ public class ScpTest extends AbstractScpTestSupport { Files.delete(zeroRemote); } - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); - String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote); + String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote); + try (CloseableScpClient scp = createCloseableScpClient()) { scp.upload(zeroLocal.toString(), remotePath); - assertFileLength(zeroRemote, 0L, DEFAULT_TIMEOUT); } + assertFileLength(zeroRemote, 0L, DEFAULT_TIMEOUT); } @Test @@ -269,12 +266,11 @@ public class ScpTest extends AbstractScpTestSupport { } assertEquals("Non-zero size for remote file=" + zeroRemote, 0L, Files.size(zeroRemote)); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); - String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote); + String remotePath = CommonTestSupportUtils.resolveRelativeRemotePath(targetPath.getParent(), zeroRemote); + try (CloseableScpClient scp = createCloseableScpClient()) { scp.download(remotePath, zeroLocal.toString()); - assertFileLength(zeroLocal, 0L, DEFAULT_TIMEOUT); } + assertFileLength(zeroLocal, 0L, DEFAULT_TIMEOUT); } @Test @@ -295,8 +291,7 @@ public class ScpTest extends AbstractScpTestSupport { Path remoteDir = scpRoot.resolve("remote"); Path remoteOutFile = remoteDir.resolve(localOutFile.getFileName()); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { CommonTestSupportUtils.writeFile(localOutFile, data); assertFalse("Remote folder already exists: " + remoteDir, Files.exists(remoteDir)); @@ -335,8 +330,7 @@ public class ScpTest extends AbstractScpTestSupport { // see SSHD-822 assumeNotIoServiceProvider(EnumSet.of(BuiltinIoServiceFactoryFactories.MINA, BuiltinIoServiceFactoryFactories.NETTY)); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -409,8 +403,7 @@ public class ScpTest extends AbstractScpTestSupport { @Test public void testScpNativeOnRecursiveDirs() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -444,8 +437,7 @@ public class ScpTest extends AbstractScpTestSupport { @Test // see SSHD-893 public void testScpNativeOnDirWithPattern() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -481,8 +473,7 @@ public class ScpTest extends AbstractScpTestSupport { Files.createDirectories(remoteDir); sshd.setFileSystemFactory(new VirtualFileSystemFactory(remoteDir)); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, ScpHelper.SCP_COMMAND_PREFIX, getClass().getSimpleName(), getCurrentTestName()); @@ -519,8 +510,7 @@ public class ScpTest extends AbstractScpTestSupport { @Test public void testScpNativeOnMixedDirAndFiles() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -559,8 +549,7 @@ public class ScpTest extends AbstractScpTestSupport { @Test public void testScpNativePreserveAttributes() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -617,8 +606,7 @@ public class ScpTest extends AbstractScpTestSupport { @Test public void testStreamsUploadAndDownload() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClient scp = createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, @@ -770,9 +758,7 @@ public class ScpTest extends AbstractScpTestSupport { } }); - try (ClientSession session = createAuthenticatedClientSession()) { - ScpClientCreator creator = ScpClientCreator.instance(); - ScpClient scp = creator.createScpClient(session); + try (CloseableScpClient scp = createCloseableScpClient()) { Path targetPath = detectTargetFolder(); Path parentPath = targetPath.getParent(); Path scpRoot = CommonTestSupportUtils.resolve(targetPath, diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java similarity index 55% copy from sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java copy to sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java index 0171a88..eec17c5 100644 --- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/CloseableScpClient.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/FullAccessSftpClient.java @@ -17,15 +17,23 @@ * under the License. */ -package org.apache.sshd.scp.client; +package org.apache.sshd.sftp.client; -import java.nio.channels.Channel; +import org.apache.sshd.common.util.closeable.AutoCloseableDelegateInvocationHandler; /** - * An {@link ScpClient} wrapper that also closes the underlying session when closed - * + * Provides both structured and raw SFTP access + * * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public interface CloseableScpClient extends ScpClient, Channel { - // Marker interface +public interface FullAccessSftpClient extends SftpClient, RawSftpClient { + static SftpClient singleSessionInstance(SftpClient client) { + if (client instanceof FullAccessSftpClient) { + return AutoCloseableDelegateInvocationHandler.wrapDelegateCloseable( + client, FullAccessSftpClient.class, client.getClientSession()); + } else { + return AutoCloseableDelegateInvocationHandler.wrapDelegateCloseable( + client, SftpClient.class, client.getClientSession()); + } + } } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java index d20c03f..28ebef2 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/SftpClient.java @@ -978,4 +978,14 @@ public interface SftpClient extends SubsystemClient { * @see #getServerExtensions() */ SftpClientExtension getExtension(String extensionName); + + /** + * Creates an {@link SftpClient} instance that also closes the underlying {@link #getClientSession() client session} + * when the client instance is closed. + * + * @return The wrapper instance + */ + default SftpClient singleSessionInstance() { + return FullAccessSftpClient.singleSessionInstance(this); + } } diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java index 7e48fb3..c36049e 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/AbstractSftpClient.java @@ -44,8 +44,7 @@ import org.apache.sshd.common.util.ValidateUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; import org.apache.sshd.sftp.SftpModuleProperties; -import org.apache.sshd.sftp.client.RawSftpClient; -import org.apache.sshd.sftp.client.SftpClient; +import org.apache.sshd.sftp.client.FullAccessSftpClient; import org.apache.sshd.sftp.client.extensions.BuiltinSftpClientExtensions; import org.apache.sshd.sftp.client.extensions.SftpClientExtension; import org.apache.sshd.sftp.client.extensions.SftpClientExtensionFactory; @@ -57,7 +56,7 @@ import org.apache.sshd.sftp.common.extensions.ParserUtils; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public abstract class AbstractSftpClient extends AbstractSubsystemClient implements SftpClient, RawSftpClient { +public abstract class AbstractSftpClient extends AbstractSubsystemClient implements FullAccessSftpClient { public static final int INIT_COMMAND_SIZE = Byte.BYTES /* command */ + Integer.BYTES /* version */; private final Attributes fileOpenAttributes = new Attributes(); diff --git a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java index 71198b2..8cf82b0 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/sftp/client/impl/SimpleSftpClientImpl.java @@ -20,7 +20,6 @@ package org.apache.sshd.sftp.client.impl; import java.io.IOException; -import java.lang.reflect.Proxy; import java.net.SocketAddress; import java.security.KeyPair; @@ -33,8 +32,10 @@ import org.apache.sshd.sftp.client.SftpClient; import org.apache.sshd.sftp.client.SftpClientFactory; import org.apache.sshd.sftp.client.SimpleSftpClient; +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleSftpClient { - private SimpleClient clientInstance; private SftpClientFactory sftpClientFactory; @@ -97,15 +98,20 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS try { SftpClient client = sftpClientFactory.createSftpClient(session); try { - return createSftpClient(session, client); + SftpClient closer = client.singleSessionInstance(); + client = null; // disable auto-close at finally block + return closer; } catch (Exception e) { err = GenericUtils.accumulateException(err, e); - try { - client.close(); - } catch (Exception t) { - debug("createSftpClient({}) failed ({}) to close client: {}", - session, t.getClass().getSimpleName(), t.getMessage(), t); - err = GenericUtils.accumulateException(err, t); + } finally { + if (client != null) { + try { + client.close(); + } catch (Exception t) { + debug("createSftpClient({}) failed ({}) to close client: {}", + session, t.getClass().getSimpleName(), t.getMessage(), t); + err = GenericUtils.accumulateException(err, t); + } } } } catch (Exception e) { @@ -113,7 +119,7 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS } // This point is reached if error occurred - log.warn("createSftpClient({}) failed ({}) to create session: {}", + log.warn("createSftpClient({}) failed ({}) to create client: {}", session, err.getClass().getSimpleName(), err.getMessage()); try { @@ -131,40 +137,6 @@ public class SimpleSftpClientImpl extends AbstractLoggingBean implements SimpleS } } - protected SftpClient createSftpClient(ClientSession session, SftpClient client) throws IOException { - ClassLoader loader = SftpClient.class.getClassLoader(); - Class<?>[] interfaces = { SftpClient.class }; - return (SftpClient) Proxy.newProxyInstance(loader, interfaces, (proxy, method, args) -> { - Throwable err = null; - Object result = null; - String name = method.getName(); - try { - result = method.invoke(client, args); - } catch (Throwable t) { - debug("invoke(SftpClient#{}) failed ({}) to execute: {}", - name, t.getClass().getSimpleName(), t.getMessage(), t); - err = GenericUtils.accumulateException(err, t); - } - - // propagate the "close" call to the session as well - if ("close".equals(name) && GenericUtils.isEmpty(args)) { - try { - session.close(); - } catch (Throwable t) { - debug("invoke(ClientSession#{}) failed ({}) to execute: {}", - name, t.getClass().getSimpleName(), t.getMessage(), t); - err = GenericUtils.accumulateException(err, t); - } - } - - if (err != null) { - throw err; - } - - return result; - }); - } - @Override public boolean isOpen() { return true; diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java index 9ffd69d..4d7b14e 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/AbstractSftpClientTestSupport.java @@ -109,6 +109,28 @@ public abstract class AbstractSftpClientTestSupport extends BaseTestSupport { } } + protected SftpClient createSingleSessionClient() throws IOException { + ClientSession session = createAuthenticatedClientSession(); + try { + SftpClient client = createSftpClient(session); + try { + SftpClient closer = client.singleSessionInstance(); + // avoid auto-close at finally clause + client = null; + session = null; + return closer; + } finally { + if (client != null) { + client.close(); + } + } + } finally { + if (session != null) { + session.close(); + } + } + } + protected SftpClient createSftpClient(ClientSession session) throws IOException { return SftpClientFactory.instance().createSftpClient(session); } diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java index 415b3c5..c920cf5 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/SftpTest.java @@ -169,24 +169,21 @@ public class SftpTest extends AbstractSftpClientTestSupport { rnd.fill(expectedRandom); byte[] expectedText = (getClass().getName() + "#" + getCurrentTestName()).getBytes(StandardCharsets.UTF_8); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { - String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile); - - try (CloseableHandle handle = sftp.open( - file, OpenMode.Create, OpenMode.Write, OpenMode.Read, OpenMode.Append)) { - sftp.write(handle, 7365L, expectedRandom); - byte[] actualRandom = new byte[expectedRandom.length]; - int readLen = sftp.read(handle, 0L, actualRandom); - assertEquals("Incomplete random data read", expectedRandom.length, readLen); - assertArrayEquals("Mismatched read random data", expectedRandom, actualRandom); - - sftp.write(handle, 3777347L, expectedText); - byte[] actualText = new byte[expectedText.length]; - readLen = sftp.read(handle, actualRandom.length, actualText); - assertEquals("Incomplete text data read", actualText.length, readLen); - assertArrayEquals("Mismatched read text data", expectedText, actualText); - } + String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile); + try (SftpClient sftp = createSingleSessionClient(); + CloseableHandle handle = sftp.open( + file, OpenMode.Create, OpenMode.Write, OpenMode.Read, OpenMode.Append)) { + sftp.write(handle, 7365L, expectedRandom); + byte[] actualRandom = new byte[expectedRandom.length]; + int readLen = sftp.read(handle, 0L, actualRandom); + assertEquals("Incomplete random data read", expectedRandom.length, readLen); + assertArrayEquals("Mismatched read random data", expectedRandom, actualRandom); + + sftp.write(handle, 3777347L, expectedText); + byte[] actualText = new byte[expectedText.length]; + readLen = sftp.read(handle, actualRandom.length, actualText); + assertEquals("Incomplete text data read", actualText.length, readLen); + assertArrayEquals("Mismatched read text data", expectedText, actualText); } byte[] actualBytes = Files.readAllBytes(testFile); @@ -214,29 +211,27 @@ public class SftpTest extends AbstractSftpClientTestSupport { rnd.fill(expected); Files.write(testFile, expected); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { - String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile); - byte[] actual = new byte[expected.length]; - int maxAllowed = actual.length / 4; - // allow less than actual - SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, maxAllowed); - try (CloseableHandle handle = sftp.open(file, OpenMode.Read)) { - int readLen = sftp.read(handle, 0L, actual); - assertEquals("Mismatched read len", maxAllowed, readLen); - - for (int index = 0; index < readLen; index++) { - byte expByte = expected[index]; - byte actByte = actual[index]; - if (expByte != actByte) { - fail("Mismatched values at index=" + index - + ": expected=0x" + Integer.toHexString(expByte & 0xFF) - + ", actual=0x" + Integer.toHexString(actByte & 0xFF)); - } + String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile); + byte[] actual = new byte[expected.length]; + int maxAllowed = actual.length / 4; + // allow less than actual + SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.set(sshd, maxAllowed); + try (SftpClient sftp = createSingleSessionClient(); + CloseableHandle handle = sftp.open(file, OpenMode.Read)) { + int readLen = sftp.read(handle, 0L, actual); + assertEquals("Mismatched read len", maxAllowed, readLen); + + for (int index = 0; index < readLen; index++) { + byte expByte = expected[index]; + byte actByte = actual[index]; + if (expByte != actByte) { + fail("Mismatched values at index=" + index + + ": expected=0x" + Integer.toHexString(expByte & 0xFF) + + ", actual=0x" + Integer.toHexString(actByte & 0xFF)); } - } finally { - SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd); } + } finally { + SftpModuleProperties.MAX_READDATA_PACKET_LENGTH.remove(sshd); } } @@ -256,8 +251,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { } }); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { String rootDir = sftp.canonicalPath("/"); String upDir = sftp.canonicalPath(rootDir + "/.."); assertEquals("Mismatched root dir parent", rootDir, upDir); @@ -281,39 +275,36 @@ public class SftpTest extends AbstractSftpClientTestSupport { lclSftp = assertHierarchyTargetFolderExists(lclSftp); sshd.setFileSystemFactory(new VirtualFileSystemFactory(lclSftp)); - try (ClientSession session = createAuthenticatedClientSession()) { - String escapePath; - if (useAbsolutePath) { - escapePath = targetPath.toString(); - if (OsUtils.isWin32()) { - escapePath = "/" + escapePath.replace(File.separatorChar, '/'); - } - } else { - Path parent = lclSftp.getParent(); - Path forbidden = Files.createDirectories(parent.resolve("forbidden")); - escapePath = "../" + forbidden.getFileName(); + String escapePath; + if (useAbsolutePath) { + escapePath = targetPath.toString(); + if (OsUtils.isWin32()) { + escapePath = "/" + escapePath.replace(File.separatorChar, '/'); } + } else { + Path parent = lclSftp.getParent(); + Path forbidden = Files.createDirectories(parent.resolve("forbidden")); + escapePath = "../" + forbidden.getFileName(); + } - try (SftpClient sftp = createSftpClient(session)) { - SftpClient.Attributes attrs = sftp.stat(escapePath); - fail("Unexpected escape success for path=" + escapePath + ": " + attrs); - } catch (SftpException e) { - int expected = OsUtils.isWin32() || (!useAbsolutePath) - ? SftpConstants.SSH_FX_INVALID_FILENAME - : SftpConstants.SSH_FX_NO_SUCH_FILE; - assertEquals("Mismatched status for " + escapePath, - SftpConstants.getStatusName(expected), - SftpConstants.getStatusName(e.getStatus())); - } + try (SftpClient sftp = createSingleSessionClient()) { + SftpClient.Attributes attrs = sftp.stat(escapePath); + fail("Unexpected escape success for path=" + escapePath + ": " + attrs); + } catch (SftpException e) { + int expected = OsUtils.isWin32() || (!useAbsolutePath) + ? SftpConstants.SSH_FX_INVALID_FILENAME + : SftpConstants.SSH_FX_NO_SUCH_FILE; + assertEquals("Mismatched status for " + escapePath, + SftpConstants.getStatusName(expected), + SftpConstants.getStatusName(e.getStatus())); } } @Test public void testNormalizeRemoteRootValues() throws Exception { - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { - StringBuilder sb = new StringBuilder(Long.SIZE + 1); + try (SftpClient sftp = createSingleSessionClient()) { String expected = sftp.canonicalPath("/"); + StringBuilder sb = new StringBuilder(Long.SIZE + 1); for (int i = 0; i < Long.SIZE; i++) { if (sb.length() > 0) { sb.setLength(0); @@ -342,8 +333,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { Factory<? extends Random> factory = client.getRandomFactory(); Random rnd = factory.create(); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { StringBuilder sb = new StringBuilder(file.length() + comps.length); String expected = sftp.canonicalPath(file); for (int i = 0; i < file.length(); i++) { @@ -392,86 +382,83 @@ public class SftpTest extends AbstractSftpClientTestSupport { File javaFile = testFile.toFile(); assertHierarchyTargetFolderExists(javaFile.getParentFile()); - try (ClientSession session = createAuthenticatedClientSession()) { - javaFile.createNewFile(); - javaFile.setWritable(false, false); - javaFile.setReadable(false, false); + javaFile.createNewFile(); + javaFile.setWritable(false, false); + javaFile.setReadable(false, false); + try (SftpClient sftp = createSingleSessionClient()) { + boolean isWindows = OsUtils.isWin32(); - try (SftpClient sftp = createSftpClient(session)) { - boolean isWindows = OsUtils.isWin32(); - - try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { - // NOTE: on Windows files are always readable - // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/ - // luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java - assertTrue("Empty read should have failed on " + file, isWindows); - } catch (IOException e) { - if (isWindows) { - throw e; - } - } - - try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - fail("Empty write should have failed on " + file); - } catch (IOException e) { - // ok + try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { + // NOTE: on Windows files are always readable + // see https://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/ + // luni/src/test/api/windows/org/apache/harmony/luni/tests/java/io/WinFileTest.java + assertTrue("Empty read should have failed on " + file, isWindows); + } catch (IOException e) { + if (isWindows) { + throw e; } + } - try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) { - // NOTE: on Windows files are always readable - assertTrue("Empty truncate should have failed on " + file, isWindows); - } catch (IOException e) { - // ok - } + try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { + fail("Empty write should have failed on " + file); + } catch (IOException e) { + // ok + } + try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate))) { // NOTE: on Windows files are always readable - int perms = sftp.stat(file).getPermissions(); - int readMask = isWindows ? 0 : SftpConstants.S_IRUSR; - int permsMask = SftpConstants.S_IWUSR | readMask; - assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, perms & permsMask); + assertTrue("Empty truncate should have failed on " + file, isWindows); + } catch (IOException e) { + // ok + } - javaFile.setWritable(true, false); + // NOTE: on Windows files are always readable + int perms = sftp.stat(file).getPermissions(); + int readMask = isWindows ? 0 : SftpConstants.S_IRUSR; + int permsMask = SftpConstants.S_IWUSR | readMask; + assertEquals("Mismatched permissions for " + file + ": 0x" + Integer.toHexString(perms), 0, perms & permsMask); - try (SftpClient.CloseableHandle h = sftp.open( - file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) { - // OK should succeed - assertTrue("Handle not marked as open for file=" + file, h.isOpen()); - } + javaFile.setWritable(true, false); - byte[] d = "0123456789\n".getBytes(StandardCharsets.UTF_8); - try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - sftp.write(h, 0, d, 0, d.length); - sftp.write(h, d.length, d, 0, d.length); - } + try (SftpClient.CloseableHandle h = sftp.open( + file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write))) { + // OK should succeed + assertTrue("Handle not marked as open for file=" + file, h.isOpen()); + } - try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - sftp.write(h, d.length * 2, d, 0, d.length); - } + byte[] d = "0123456789\n".getBytes(StandardCharsets.UTF_8); + try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { + sftp.write(h, 0, d, 0, d.length); + sftp.write(h, d.length, d, 0, d.length); + } - try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { - byte[] overwrite = "-".getBytes(StandardCharsets.UTF_8); - sftp.write(h, 3L, overwrite, 0, 1); - d[3] = overwrite[0]; - } + try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { + sftp.write(h, d.length * 2, d, 0, d.length); + } - try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { - // NOTE: on Windows files are always readable - assertTrue("Data read should have failed on " + file, isWindows); - } catch (IOException e) { - if (isWindows) { - throw e; - } + try (SftpClient.CloseableHandle h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write))) { + byte[] overwrite = "-".getBytes(StandardCharsets.UTF_8); + sftp.write(h, 3L, overwrite, 0, 1); + d[3] = overwrite[0]; + } + + try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { + // NOTE: on Windows files are always readable + assertTrue("Data read should have failed on " + file, isWindows); + } catch (IOException e) { + if (isWindows) { + throw e; } + } - javaFile.setReadable(true, false); + javaFile.setReadable(true, false); - byte[] buf = new byte[3]; - try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { - int l = sftp.read(h, 2L, buf, 0, buf.length); - String expected = new String(d, 2, l, StandardCharsets.UTF_8); - String actual = new String(buf, 0, l, StandardCharsets.UTF_8); - assertEquals("Mismatched read data", expected, actual); - } + byte[] buf = new byte[3]; + try (SftpClient.CloseableHandle h = sftp.open(file /* no mode == read */)) { + int l = sftp.read(h, 2L, buf, 0, buf.length); + String expected = new String(d, 2, l, StandardCharsets.UTF_8); + String actual = new String(buf, 0, l, StandardCharsets.UTF_8); + assertEquals("Mismatched read data", expected, actual); } } } @@ -487,28 +474,26 @@ public class SftpTest extends AbstractSftpClientTestSupport { String file = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, testFile); assertHierarchyTargetFolderExists(testFile.getParent()); - try (ClientSession session = createAuthenticatedClientSession()) { - Files.deleteIfExists(testFile); // make sure starting fresh - Files.createFile(testFile, IoUtils.EMPTY_FILE_ATTRIBUTES); + Files.deleteIfExists(testFile); // make sure starting fresh + Files.createFile(testFile, IoUtils.EMPTY_FILE_ATTRIBUTES); - try (SftpClient sftp = createSftpClient(session)) { - Collection<PosixFilePermission> initialPermissions = IoUtils.getPermissions(testFile); - assertTrue("File does not have enough initial permissions: " + initialPermissions, - initialPermissions.containsAll( - EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))); + try (SftpClient sftp = createSingleSessionClient()) { + Collection<PosixFilePermission> initialPermissions = IoUtils.getPermissions(testFile); + assertTrue("File does not have enough initial permissions: " + initialPermissions, + initialPermissions.containsAll( + EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))); - try (CloseableHandle handle = sendRawAttributeImpactOpen(file, sftp)) { - outputDebugMessage("%s - handle=%s", getCurrentTestName(), handle); - } - - Collection<PosixFilePermission> updatedPermissions = IoUtils.getPermissions(testFile); - assertEquals("Mismatched updated permissions count", initialPermissions.size(), updatedPermissions.size()); - assertTrue("File does not preserve initial permissions: expected=" + initialPermissions + ", actual=" - + updatedPermissions, - updatedPermissions.containsAll(initialPermissions)); - } finally { - Files.delete(testFile); + try (CloseableHandle handle = sendRawAttributeImpactOpen(file, sftp)) { + outputDebugMessage("%s - handle=%s", getCurrentTestName(), handle); } + + Collection<PosixFilePermission> updatedPermissions = IoUtils.getPermissions(testFile); + assertEquals("Mismatched updated permissions count", initialPermissions.size(), updatedPermissions.size()); + assertTrue("File does not preserve initial permissions: expected=" + initialPermissions + ", actual=" + + updatedPermissions, + updatedPermissions.containsAll(initialPermissions)); + } finally { + Files.delete(testFile); } } @@ -552,8 +537,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { byte[] data = (getClass().getName() + "#" + getCurrentTestName() + "[" + localFile + "]").getBytes(StandardCharsets.UTF_8); Files.write(localFile, data, StandardOpenOption.CREATE); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session); + try (SftpClient sftp = createSingleSessionClient(); InputStream stream = sftp.read( CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) { byte[] expected = new byte[data.length / 4]; @@ -626,8 +610,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "[" + localFile + "]") .getBytes(StandardCharsets.UTF_8); Files.write(localFile, expected, StandardOpenOption.CREATE); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { byte[] actual = new byte[expected.length]; try (InputStream stream = sftp.read( CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, localFile), OpenMode.Read)) { @@ -865,8 +848,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { }; factory.addSftpEventListener(listener); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { assertEquals("Mismatched negotiated version", sftp.getVersion(), versionHolder.get()); testClient(client, sftp); @@ -895,51 +877,49 @@ public class SftpTest extends AbstractSftpClientTestSupport { */ @Test public void testWriteChunking() throws Exception { - try (ClientSession session = createAuthenticatedClientSession()) { - Path targetPath = detectTargetFolder(); - Path lclSftp = CommonTestSupportUtils.resolve( - targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); - CommonTestSupportUtils.deleteRecursive(lclSftp); + Path targetPath = detectTargetFolder(); + Path lclSftp = CommonTestSupportUtils.resolve( + targetPath, SftpConstants.SFTP_SUBSYSTEM_NAME, getClass().getSimpleName(), getCurrentTestName()); + CommonTestSupportUtils.deleteRecursive(lclSftp); - Path parentPath = targetPath.getParent(); - Path clientFolder = assertHierarchyTargetFolderExists(lclSftp).resolve("client"); - String dir = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder); + Path parentPath = targetPath.getParent(); + Path clientFolder = assertHierarchyTargetFolderExists(lclSftp).resolve("client"); + String dir = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, clientFolder); - try (SftpClient sftp = createSftpClient(session)) { - sftp.mkdir(dir); - - uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt"); - - // Make sure sizes should invoke our internal chunking mechanism - ClientChannel clientChannel = sftp.getClientChannel(); - SftpModuleProperties.WRITE_CHUNK_SIZE.set(clientChannel, - Math.min(SftpClient.IO_BUFFER_SIZE, SftpModuleProperties.WRITE_CHUNK_SIZE.getRequiredDefault()) - - Byte.MAX_VALUE); - - uploadAndVerifyFile(sftp, clientFolder, dir, - SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT - 1, "bufferMaxLenMinusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, "bufferMaxLenFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT + 1, "bufferMaxLenPlusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - (int) (1.5 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT), "1point5BufferMaxLenFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - (2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) - 1, "2TimesBufferMaxLenMinusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - 2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, "2TimesBufferMaxLenFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, - (2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) + 1, "2TimesBufferMaxLenPlusOneFile.txt"); - uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt"); - - // test erroneous calls that check for negative values - Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid"); - testInvalidParams(sftp, invalidPath, CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, invalidPath)); - - // cleanup - sftp.rmdir(dir); - } + try (SftpClient sftp = createSingleSessionClient()) { + sftp.mkdir(dir); + + uploadAndVerifyFile(sftp, clientFolder, dir, 0, "emptyFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, 1000, "smallFile.txt"); + + // Make sure sizes should invoke our internal chunking mechanism + ClientChannel clientChannel = sftp.getClientChannel(); + SftpModuleProperties.WRITE_CHUNK_SIZE.set(clientChannel, + Math.min(SftpClient.IO_BUFFER_SIZE, SftpModuleProperties.WRITE_CHUNK_SIZE.getRequiredDefault()) + - Byte.MAX_VALUE); + + uploadAndVerifyFile(sftp, clientFolder, dir, + SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT - 1, "bufferMaxLenMinusOneFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, "bufferMaxLenFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT + 1, "bufferMaxLenPlusOneFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + (int) (1.5 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT), "1point5BufferMaxLenFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + (2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) - 1, "2TimesBufferMaxLenMinusOneFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + 2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT, "2TimesBufferMaxLenFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, + (2 * SshConstants.SSH_REQUIRED_TOTAL_PACKET_LENGTH_SUPPORT) + 1, "2TimesBufferMaxLenPlusOneFile.txt"); + uploadAndVerifyFile(sftp, clientFolder, dir, 200000, "largerFile.txt"); + + // test erroneous calls that check for negative values + Path invalidPath = clientFolder.resolve(getCurrentTestName() + "-invalid"); + testInvalidParams(sftp, invalidPath, CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, invalidPath)); + + // cleanup + sftp.rmdir(dir); } } @@ -1125,8 +1105,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { Path parentPath = targetPath.getParent(); Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client")); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { Path file1 = clientFolder.resolve("file-1.txt"); String file1Path = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file1); try (OutputStream os = sftp.write(file1Path, SftpClient.MIN_WRITE_BUFFER_SIZE)) { @@ -1163,8 +1142,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { @Test public void testServerExtensionsDeclarations() throws Exception { - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { Map<String, byte[]> extensions = sftp.getServerExtensions(); for (String name : new String[] { SftpConstants.EXT_NEWLINE, SftpConstants.EXT_VERSIONS, @@ -1497,8 +1475,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { @Test // see SSHD-903 public void testForcedVersionNegotiation() throws Exception { SftpModuleProperties.SFTP_VERSION.set(sshd, SftpConstants.SFTP_V3); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { assertEquals("Mismatched negotiated version", SftpConstants.SFTP_V3, sftp.getVersion()); } } @@ -1547,8 +1524,7 @@ public class SftpTest extends AbstractSftpClientTestSupport { Path parentPath = targetPath.getParent(); Path clientFolder = assertHierarchyTargetFolderExists(lclSftp.resolve("client")); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { Path file = clientFolder.resolve("file.txt"); String filePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, file); try (OutputStream os = sftp.write(filePath, SftpClient.MIN_WRITE_BUFFER_SIZE)) { diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java index 3aaae5b..cea742a 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/UnsupportedExtensionTest.java @@ -21,7 +21,6 @@ package org.apache.sshd.sftp.client.extensions; import java.io.IOException; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; @@ -41,14 +40,13 @@ public class UnsupportedExtensionTest extends AbstractSftpClientTestSupport { @Test // see SSHD-890 public void testUnsupportedExtension() throws IOException { - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftpClient = createSftpClient(session)) { + try (SftpClient sftpClient = createSingleSessionClient()) { + RawSftpClient sftp = assertObjectInstanceOf("Not a raw SFTP client", RawSftpClient.class, sftpClient); + String opcode = getCurrentTestName(); Buffer buffer = new ByteArrayBuffer(Integer.BYTES + GenericUtils.length(opcode) + Byte.SIZE, false); buffer.putString(opcode); - assertObjectInstanceOf("Not a raw SFTP client", RawSftpClient.class, sftpClient); - RawSftpClient sftp = (RawSftpClient) sftpClient; int cmd = sftp.send(SftpConstants.SSH_FXP_EXTENDED, buffer); Buffer responseBuffer = sftp.receive(cmd); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java index af7b3ec..b192122 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractCheckFileExtensionTest.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.digest.BuiltinDigests; @@ -175,8 +174,7 @@ public class AbstractCheckFileExtensionTest extends AbstractSftpClientTestSuppor Path parentPath = targetPath.getParent(); String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); String srcFolder = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile.getParent()); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { CheckFileNameExtension file = assertExtensionCreated(sftp, CheckFileNameExtension.class); try { Map.Entry<String, ?> result = file.checkFileName(srcFolder, algorithms, 0L, 0L, hashBlockSize); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java index aa70070..460fc20 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/AbstractMD5HashExtensionTest.java @@ -29,7 +29,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.digest.Digest; import org.apache.sshd.common.util.GenericUtils; @@ -133,8 +132,7 @@ public class AbstractMD5HashExtensionTest extends AbstractSftpClientTestSupport Path parentPath = targetPath.getParent(); String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); String srcFolder = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile.getParent()); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { MD5FileExtension file = assertExtensionCreated(sftp, MD5FileExtension.class); try { byte[] actual = file.getHash(srcFolder, 0L, 0L, quickHash); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java index 8301d3c..e24458d 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyDataExtensionImplTest.java @@ -33,7 +33,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.Factory; import org.apache.sshd.common.random.Random; import org.apache.sshd.common.util.io.IoUtils; @@ -159,8 +158,7 @@ public class CopyDataExtensionImplTest extends AbstractSftpClientTestSupport { } } - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { CopyDataExtension ext = assertExtensionCreated(sftp, CopyDataExtension.class); try (CloseableHandle readHandle = sftp.open(srcPath, SftpClient.OpenMode.Read); CloseableHandle writeHandle = sftp.open(dstPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) { diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java index 08dbe67..f2e5dd3 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/CopyFileExtensionImplTest.java @@ -25,7 +25,6 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.io.IoUtils; import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport; import org.apache.sshd.sftp.client.SftpClient; @@ -71,8 +70,7 @@ public class CopyFileExtensionImplTest extends AbstractSftpClientTestSupport { LinkOption[] options = IoUtils.getLinkOptions(true); assertFalse("Destination file unexpectedly exists", Files.exists(dstFile, options)); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { CopyFileExtension ext = assertExtensionCreated(sftp, CopyFileExtension.class); ext.copyFile(srcPath, dstPath, false); assertTrue("Source file not preserved", Files.exists(srcFile, options)); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java index 1158c09..a18f17e 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/helpers/SpaceAvailableExtensionImplTest.java @@ -27,7 +27,6 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.server.channel.ChannelSession; import org.apache.sshd.server.command.Command; import org.apache.sshd.server.subsystem.SubsystemFactory; @@ -88,8 +87,7 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo } })); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { SpaceAvailableExtension ext = assertExtensionCreated(sftp, SpaceAvailableExtension.class); SpaceAvailableExtensionInfo actual = ext.available(queryPath); assertEquals("Mismatched information", expected, actual); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java index 639ff89..d0ba605 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/extensions/openssh/helpers/OpenSSHExtensionsTest.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.io.IoUtils; @@ -81,8 +80,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { Path parentPath = targetPath.getParent(); String srcPath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { OpenSSHFsyncExtension fsync = assertExtensionCreated(sftp, OpenSSHFsyncExtension.class); try (CloseableHandle fileHandle = sftp.open(srcPath, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) { sftp.write(fileHandle, 0L, expected); @@ -164,8 +162,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { } })); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { OpenSSHStatPathExtension pathStat = assertExtensionCreated(sftp, OpenSSHStatPathExtension.class); OpenSSHStatExtensionInfo actual = pathStat.stat(srcPath); String invokedExtension = extensionHolder.getAndSet(null); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java index d238ddc..12ee16b 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/fs/SftpDirectoryScannersTest.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.io.DirectoryScanner; import org.apache.sshd.common.util.io.PathUtils; import org.apache.sshd.sftp.client.SftpClient; @@ -96,8 +95,7 @@ public class SftpDirectoryScannersTest extends AbstractSftpFilesSystemSupport { List<Path> expected = setup.getExpected(); String remRoot = setup.getRemoteFilePath(); List<ScanDirEntry> actual; - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { + try (SftpClient sftp = createSingleSessionClient()) { SftpClientDirectoryScanner ds = new SftpClientDirectoryScanner(remRoot, pattern); actual = ds.scan(sftp, () -> new ArrayList<>(expected.size())); } diff --git a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java index a2d52ec..f1aa19e 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/sftp/client/impl/SftpRemotePathChannelTest.java @@ -30,7 +30,6 @@ import java.nio.file.StandardOpenOption; import java.util.Date; import java.util.EnumSet; -import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.sftp.SftpModuleProperties; import org.apache.sshd.sftp.client.AbstractSftpClientTestSupport; import org.apache.sshd.sftp.client.SftpClient; @@ -66,25 +65,23 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport { byte[] expected = (getClass().getName() + "#" + getCurrentTestName() + "(" + new Date() + ")").getBytes(StandardCharsets.UTF_8); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session)) { - Path parentPath = targetPath.getParent(); - String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile); - - try (FileChannel fc = sftp.openRemotePathChannel( - remFilePath, 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); - } + Path parentPath = targetPath.getParent(); + String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, lclFile); + + try (SftpClient sftp = createSingleSessionClient(); + FileChannel fc = sftp.openRemotePathChannel( + remFilePath, 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); @@ -114,8 +111,7 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport { Files.deleteIfExists(dstFile); String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session); + try (SftpClient sftp = createSingleSessionClient(); FileChannel srcChannel = sftp.openRemotePathChannel( remFilePath, EnumSet.of(StandardOpenOption.READ)); FileChannel dstChannel = FileChannel.open(dstFile, @@ -148,8 +144,7 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport { Files.deleteIfExists(dstFile); String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, srcFile); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session); + try (SftpClient sftp = createSingleSessionClient(); FileChannel srcChannel = sftp.openRemotePathChannel( remFilePath, EnumSet.of(StandardOpenOption.READ)); FileChannel dstChannel = FileChannel.open(dstFile, @@ -188,8 +183,7 @@ public class SftpRemotePathChannelTest extends AbstractSftpClientTestSupport { Files.deleteIfExists(dstFile); String remFilePath = CommonTestSupportUtils.resolveRelativeRemotePath(parentPath, dstFile); - try (ClientSession session = createAuthenticatedClientSession(); - SftpClient sftp = createSftpClient(session); + try (SftpClient sftp = createSingleSessionClient(); FileChannel dstChannel = sftp.openRemotePathChannel( remFilePath, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE)); FileChannel srcChannel = FileChannel.open(srcFile, StandardOpenOption.READ)) {