This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit a85462b49cc76c06b565e8d311864808995a3ac9 Author: Thomas Wolf <tw...@apache.org> AuthorDate: Thu Mar 14 23:58:50 2024 +0100 Improve javadoc on ClientChannel.setIn(InputStream) Closing that input stream when the channel closes is in my view a design mistake. The stream is obtained by the application code from somewhere, and it should be the application's responsibility to close it when appropriate. While changing the implementation (not closing the stream in AbstractClientChannel.getInnerCloseable()) would work, it appears to be too risky a change for a minor version bump. So just explain in the javadoc what to do if the stream should _not_ be closed. (For instance, System.in should never be closed and doing so may even cause hangs on Windows.) Also add a (somewhat contrived) test that chains two remote shells by setting the output of one shell as the input of another shell. --- .../apache/sshd/client/channel/ClientChannel.java | 3 +- .../sshd/server/channel/ChannelSessionTest.java | 55 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java index aad26a385..d1f809b05 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ClientChannel.java @@ -139,7 +139,8 @@ public interface ClientChannel extends Channel, StreamingChannel, ClientSessionH * {@link #getInvertedIn()} method instead and write data directly. * <p> * The stream must be set before the channel is opened. When the channel closes, it will {@link InputStream#close() - * close} the given stream. + * close} the given stream. If the stream should <em>not</em> be closed (for instance, if it is {@link System#in + * System.in}), wrap it in an {@link org.apache.sshd.common.util.io.input.NoCloseInputStream NoCloseInputStream}. * </p> * * @param in an {@link InputStream} to be polled and forwarded diff --git a/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java index 0562fb623..538de04ff 100644 --- a/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java @@ -111,6 +111,61 @@ public class ChannelSessionTest extends BaseTestSupport { } } + private void chainedCommands(ClientSession session) throws Exception { + try (ClientChannel channel = session.createChannel(Channel.CHANNEL_SHELL)) { + channel.open().verify(OPEN_TIMEOUT); + try (ClientChannel second = session.createChannel(Channel.CHANNEL_SHELL)) { + // Chain stdout of the first command to stdin of the second. + second.setIn(channel.getInvertedOut()); + second.open().verify(OPEN_TIMEOUT); + + // Write to the first command + OutputStream invertedIn = channel.getInvertedIn(); + String cmdSent = "echo foo\nexit\n"; + invertedIn.write(cmdSent.getBytes()); + invertedIn.flush(); + + long waitStart = System.currentTimeMillis(); + Collection<ClientChannelEvent> result = second.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 10_000); + long waitEnd = System.currentTimeMillis(); + assertTrue("Wrong channel state after " + (waitEnd - waitStart) + " ms.: " + result, + result.containsAll(EnumSet.of(ClientChannelEvent.CLOSED))); + // Read from the second command's stdout and check the result. + try (InputStream invertedOut = second.getInvertedOut()) { + byte[] b = new byte[1024]; + int l = invertedOut.read(b); + String cmdReceived = (l > 0) ? new String(b, 0, l) : ""; + assertEquals("Mismatched echoed command", cmdSent, cmdReceived); + } + } + } + } + + @Test + public void pipedInputStream() throws Exception { + try (SshServer server = setupTestServer(); + SshClient client = setupTestClient()) { + + server.setShellFactory(session -> new CommandExecutionHelper(null) { + @Override + protected boolean handleCommandLine(String command) throws Exception { + OutputStream out = getOutputStream(); + out.write((command + "\n").getBytes(StandardCharsets.UTF_8)); + return !"exit".equals(command); + } + }); + server.start(); + client.start(); + + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, server.getPort()) + .verify(CONNECT_TIMEOUT).getSession()) { + session.addPasswordIdentity(getCurrentTestName()); + session.auth().verify(AUTH_TIMEOUT); + chainedCommands(session); + } + } + } + @Test public void testNoFlush() throws Exception { try (SshServer server = setupTestServer();