You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2015/11/17 08:10:58 UTC
[6/6] mina-sshd git commit: [SSHD-587] Take into account reported PTY
modes when creating command input/output streams for the shell
[SSHD-587] Take into account reported PTY modes when creating command input/output streams for the shell
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/97b73947
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/97b73947
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/97b73947
Branch: refs/heads/master
Commit: 97b73947d5dbdfc777123a0f511e9966efb14556
Parents: f61edfc
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue Nov 17 09:10:39 2015 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue Nov 17 09:10:39 2015 +0200
----------------------------------------------------------------------
.../org/apache/sshd/common/channel/PtyMode.java | 383 +++++++++++++++++--
.../org/apache/sshd/common/util/OsUtils.java | 30 +-
.../common/util/buffer/ByteArrayBuffer.java | 5 +-
.../sshd/common/util/io/NullInputStream.java | 88 +++++
.../sshd/common/util/io/NullOutputStream.java | 70 ++++
.../java/org/apache/sshd/server/Command.java | 27 +-
.../apache/sshd/server/CommandLifecycle.java | 48 +++
.../java/org/apache/sshd/server/SshServer.java | 4 +-
.../sshd/server/channel/ChannelSession.java | 186 +++------
.../shell/InteractiveProcessShellFactory.java | 31 +-
.../apache/sshd/server/shell/InvertedShell.java | 23 +-
.../sshd/server/shell/InvertedShellWrapper.java | 35 +-
.../apache/sshd/server/shell/ProcessShell.java | 68 +++-
.../sshd/server/shell/ProcessShellFactory.java | 52 ++-
.../sshd/server/shell/TtyFilterInputStream.java | 121 ++++--
.../server/shell/TtyFilterOutputStream.java | 88 ++++-
.../apache/sshd/server/shell/TtyOptions.java | 74 ----
.../apache/sshd/common/channel/PtyModeTest.java | 63 +++
.../sshd/server/channel/ChannelSessionTest.java | 22 --
.../server/shell/InvertedShellWrapperTest.java | 6 +-
.../server/shell/TtyFilterInputStreamTest.java | 153 ++++----
.../server/shell/TtyFilterOutputStreamTest.java | 153 ++++----
.../sshd/util/test/BogusInvertedShell.java | 9 +-
23 files changed, 1142 insertions(+), 597 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/common/channel/PtyMode.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/channel/PtyMode.java b/sshd-core/src/main/java/org/apache/sshd/common/channel/PtyMode.java
index 83daeaa..f83cf9c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/channel/PtyMode.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/channel/PtyMode.java
@@ -18,8 +18,15 @@
*/
package org.apache.sshd.common.channel;
+import java.util.Collection;
import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
/**
* A enum describing the tty modes according to
@@ -29,38 +36,268 @@ import java.util.Map;
*/
public enum PtyMode {
- // Chars
- VINTR(1), VQUIT(2), VERASE(3), VKILL(4), VEOF(5), VEOL(6), VEOL2(7), VSTART(8), VSTOP(9),
- VSUSP(10), VDSUSP(11), VREPRINT(12), VWERASE(13), VLNEXT(14), VFLUSH(15), VSWTCH(16), VSTATUS(17), VDISCARD(18),
+ /////////////////////////////// Chars ////////////////////////////////////
+
+ /**
+ * Interrupt character; 255 if none. Similarly for the other characters.
+ * Not all of these characters are supported on all systems.
+ */
+ VINTR(1),
+ /**
+ * The quit character (sends SIGQUIT signal on POSIX systems).
+ */
+ VQUIT(2),
+ /**
+ * Erase the character to left of the cursor.
+ */
+ VERASE(3),
+ /**
+ * Kill the current input line.
+ */
+ VKILL(4),
+ /**
+ * End-of-file character (sends EOF from the terminal).
+ */
+ VEOF(5),
+ /**
+ * End-of-line character in addition to carriage return and/or line-feed.
+ */
+ VEOL(6),
+ /**
+ * Additional end-of-line character.
+ */
+ VEOL2(7),
+ /**
+ * Continues paused output (normally control-Q).
+ */
+ VSTART(8),
+ /**
+ * Pauses output (normally control-S).
+ */
+ VSTOP(9),
+ /**
+ * Suspends the current program.
+ */
+ VSUSP(10),
+ /**
+ * Another suspend character.
+ */
+ VDSUSP(11),
+ /**
+ * Reprints the current input line.
+ */
+ VREPRINT(12),
+ /**
+ * Erases a word left of cursor.
+ */
+ VWERASE(13),
+ /**
+ * Enter the next character typed literally, even if it is a special character
+ */
+ VLNEXT(14),
+ /**
+ * Character to flush output.
+ */
+ VFLUSH(15),
+ /**
+ * Switch to a different shell layer.
+ */
+ VSWTCH(16),
+ /**
+ * Prints system status line (load, command, pid, etc).
+ */
+ VSTATUS(17),
+ /**
+ * Toggles the flushing of terminal output.
+ */
+ VDISCARD(18),
+
+ ///////////////////////////////// I-flags ////////////////////////////////
+
+ /**
+ * The ignore parity flag. The parameter SHOULD be 0 if this flag
+ * is FALSE, and 1 if it is TRUE.
+ */
+ IGNPAR(30),
+ /**
+ * Mark parity and framing errors.
+ */
+ PARMRK(31),
+ /**
+ * Enable checking of parity errors.
+ */
+ INPCK(32),
+ /**
+ * Strip 8th bit off characters.
+ */
+ ISTRIP(33),
+ /**
+ * Map NL into CR on input.
+ */
+ INLCR(34),
+ /**
+ * Ignore CR on input.
+ */
+ IGNCR(35),
+ /**
+ * Map CR to NL on input.
+ */
+ ICRNL(36),
+ /**
+ * Translate uppercase characters to lowercase.
+ */
+ IUCLC(37),
+ /**
+ * Enable output flow control.
+ */
+ IXON(38),
+ /**
+ * Any char will restart after stop.
+ */
+ IXANY(39),
+ /**
+ * Enable input flow control.
+ */
+ IXOFF(40),
+ /**
+ * Ring bell on input queue full.
+ */
+ IMAXBEL(41),
+
+ /////////////////////////////// L-flags //////////////////////////////////
- // I flags
- IGNPAR(30), PARMRK(31), INPCK(32), ISTRIP(33), INLCR(34), IGNCR(35), ICRNL(36), IUCLC(37), IXON(38), IXANY(39),
- IXOFF(40), IMAXBEL(41),
+ /**
+ * Enable signals INTR, QUIT, [D]SUSP.
+ */
+ ISIG(50),
+ /**
+ * Canonicalize input lines.
+ */
+ ICANON(51),
+ /**
+ * Enable input and output of uppercase characters by preceding their
+ * lowercase equivalents with "\".
+ */
+ XCASE(52),
+ /**
+ * Enable echoing.
+ */
+ ECHO(53),
+ /**
+ * Visually erase chars.
+ */
+ ECHOE(54),
+ /**
+ * Kill character discards current line.
+ */
+ ECHOK(55),
+ /**
+ * Echo NL even if ECHO is off.
+ */
+ ECHONL(56),
+ /**
+ * Don't flush after interrupt.
+ */
+ NOFLSH(57),
+ /**
+ * Stop background jobs from output.
+ */
+ TOSTOP(58),
+ /**
+ * Enable extensions.
+ */
+ IEXTEN(59),
+ /**
+ * Echo control characters as ^(Char).
+ */
+ ECHOCTL(60),
+ /**
+ * Visual erase for line kill.
+ */
+ ECHOKE(61),
+ /**
+ * Retype pending input.
+ */
+ PENDIN(62),
- // L flags
- ISIG(50), ICANON(51), XCASE(52), ECHO(53), ECHOE(54), ECHOK(55), ECHONL(56), NOFLSH(57), TOSTOP(58), IEXTEN(59),
- ECHOCTL(60), ECHOKE(61), PENDIN(62),
+ /////////////////////////// O-flags //////////////////////////////////////
- // O flags
- OPOST(70), OLCUC(71), ONLCR(72), OCRNL(73), ONOCR(74), ONLRET(75),
+ /**
+ * Enable output processing.
+ */
+ OPOST(70),
+ /**
+ * Convert lowercase to uppercase.
+ */
+ OLCUC(71),
+ /**
+ * Map NL to CR-NL.
+ */
+ ONLCR(72),
+ /**
+ * Translate carriage return to newline (output).
+ */
+ OCRNL(73),
+ /**
+ * Translate newline to carriage return-newline (output).
+ */
+ ONOCR(74),
+ /**
+ * Newline performs a carriage return (output).
+ */
+ ONLRET(75),
- // C flags
- CS7(90), CS8(91), PARENB(92), PARODD(93),
+ //////////////////////////////// C-flags /////////////////////////////////
- // Speeed
- TTY_OP_ISPEED(128), TTY_OP_OSPEED(129);
+ /**
+ * 7 bit mode.
+ */
+ CS7(90),
+ /**
+ * 8 bit mode.
+ */
+ CS8(91),
+ /**
+ * Parity enable.
+ */
+ PARENB(92),
+ /**
+ * Odd parity, else even.
+ */
+ PARODD(93),
+
+ /////////////////////////// Speed(s) /////////////////////////////////////
+
+ /**
+ * Specifies the input baud rate in bits per second.
+ */
+ TTY_OP_ISPEED(128),
+ /**
+ * Specifies the output baud rate in bits per second.
+ */
+ TTY_OP_OSPEED(129);
public static final byte TTY_OP_END = 0x00;
- private static final Map<Integer, PtyMode> COMMANDS;
+ // objects that can be used to set {@link PtyMode}s as {@code true} or {@code false}
+ public static final Integer FALSE_SETTING = Integer.valueOf(0);
+ public static final Integer TRUE_SETTING = Integer.valueOf(1);
- static {
- Map<Integer, PtyMode> commands = new java.util.HashMap<>();
- for (PtyMode c : PtyMode.values()) {
- commands.put(c.toInt(), c);
- }
- COMMANDS = Collections.unmodifiableMap(commands);
- }
+ /**
+ * An un-modifiable {@link Set} of all defined {@link PtyMode}s
+ */
+ public static final Set<PtyMode> MODES = Collections.unmodifiableSet(EnumSet.allOf(PtyMode.class));
+
+ private static final Map<Integer, PtyMode> COMMANDS =
+ Collections.unmodifiableMap(new HashMap<Integer, PtyMode>(MODES.size()) {
+ private static final long serialVersionUID = 1L; // we're not serializing it
+
+ {
+ for (PtyMode c : PtyMode.values()) {
+ put(c.toInt(), c);
+ }
+ }
+ });
private int v;
@@ -72,7 +309,105 @@ public enum PtyMode {
return v;
}
+ /**
+ * @param b The numeric value of the option
+ * @return The matching {@link PtyMode} or {@code null} if no match found
+ * @see #toInt()
+ */
public static PtyMode fromInt(int b) {
- return COMMANDS.get(0x00FF & (b + 0x100));
+ return COMMANDS.get(0x00FF & b);
+ }
+
+ /**
+ * @param options The options to enable - ignored if {@code null}/empty
+ * @return A {@link Map} where all the specified {@link PtyMode}s have {@link #TRUE_SETTING}
+ */
+ public static Map<PtyMode, Integer> createEnabledOptions(PtyMode ... options) {
+ return createEnabledOptions(GenericUtils.of(options));
+ }
+
+ /**
+ * @param options The options to enable - ignored if {@code null}/empty
+ * @return A {@link Map} where all the specified {@link PtyMode}s have {@link #TRUE_SETTING}
+ */
+ public static Map<PtyMode, Integer> createEnabledOptions(Collection<PtyMode> options) {
+ if (GenericUtils.isEmpty(options)) {
+ return Collections.emptyMap();
+ }
+
+ Map<PtyMode, Integer> modes = new EnumMap<>(PtyMode.class);
+ for (PtyMode m : options) {
+ modes.put(m, TRUE_SETTING);
+ }
+
+ return modes;
+ }
+
+ public static Set<PtyMode> resolveEnabledOptions(Map<PtyMode, ?> modes, PtyMode ... options) {
+ return resolveEnabledOptions(modes, GenericUtils.of(options));
+ }
+
+ /**
+ * @param modes The PTY settings - ignored if {@code null}/empty
+ * @param options The options that should be enabled
+ * @return A {@link Set} of all the {@link PtyMode}s that appeared
+ * in the settings and were enabled
+ * @see #getBooleanSettingValue(Map, PtyMode)
+ */
+ public static Set<PtyMode> resolveEnabledOptions(Map<PtyMode, ?> modes, Collection<PtyMode> options) {
+ if (GenericUtils.isEmpty(modes) || GenericUtils.isEmpty(options)) {
+ return Collections.emptySet();
+ }
+
+ Set<PtyMode> enabled = EnumSet.noneOf(PtyMode.class);
+ for (PtyMode m : options) {
+ if (getBooleanSettingValue(modes, m)) {
+ enabled.add(m);
+ }
+ }
+
+ return enabled;
+ }
+
+ /**
+ * @param modes The current modes {@link Map}-ing
+ * @param m The required {@link PtyMode}
+ * @return {@code true} if <U>all</U> of these conditions hold:</BR>
+ * <UL>
+ * <LI>Modes map is not {@code null}/empty</LI>
+ * <LI>Required mode setting is not {@code null}</LI>
+ * <LI>The setting has a mapped value</LI>
+ * <LI>The mapped value is a {@link Number}</LI>
+ * <LI>The {@link Number#intValue()} is non-zero</LI>
+ * </UL>
+ * @see #getBooleanSettingValue(Object)
+ */
+ public static boolean getBooleanSettingValue(Map<PtyMode, ?> modes, PtyMode m) {
+ if ((m == null) || GenericUtils.isEmpty(modes)) {
+ return false;
+ } else {
+ return getBooleanSettingValue(modes.get(m));
+ }
+ }
+
+ /**
+ * @param v The value to be tested
+ * @return {@code true} if <U>all</U> of these conditions hold:</BR>
+ * <UL>
+ * <LI>The mapped value is a {@link Number}</LI>
+ * <LI>The {@link Number#intValue()} is non-zero</LI>
+ * </UL>
+ * @see #getBooleanSettingValue(int)
+ */
+ public static boolean getBooleanSettingValue(Object v) {
+ return (v instanceof Number) && getBooleanSettingValue(((Number) v).intValue());
+ }
+
+ /**
+ * @param v The setting value
+ * @return {@code true} if value is non-zero
+ */
+ public static boolean getBooleanSettingValue(int v) {
+ return v != 0;
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/common/util/OsUtils.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/OsUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/OsUtils.java
index ad023e7..24f2fdd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/OsUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/OsUtils.java
@@ -18,24 +18,30 @@
*/
package org.apache.sshd.common.util;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
/**
* Operating system dependent utility methods.
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public final class OsUtils {
+ public static final String WINDOWS_SHELL_COMMAND_NAME = "cmd.exe";
+ public static final String LINUX_SHELL_COMMAND_NAME = "/bin/sh";
+
+ public static final List<String> LINUX_COMMAND =
+ Collections.unmodifiableList(Arrays.asList(LINUX_SHELL_COMMAND_NAME, "-i", "-l"));
+ public static final List<String> WINDOWS_COMMAND =
+ Collections.unmodifiableList(Collections.singletonList(WINDOWS_SHELL_COMMAND_NAME));
- private static final boolean WIN32;
+ private static final boolean WIN32 = GenericUtils.trimToEmpty(System.getProperty("os.name")).toLowerCase().contains("windows");
private OsUtils() {
throw new UnsupportedOperationException("No instance allowed");
}
- static {
- String os = System.getProperty("os.name").toLowerCase();
- WIN32 = os.contains("windows");
- }
-
/**
* @return true if the host is a UNIX system (and not Windows).
*/
@@ -50,4 +56,16 @@ public final class OsUtils {
return WIN32;
}
+ public static List<String> resolveDefaultInteractiveCommand() {
+ return resolveInteractiveCommand(isWin32());
+ }
+
+ public static List<String> resolveInteractiveCommand(boolean isWin32) {
+ if (isWin32) {
+ return WINDOWS_COMMAND;
+ } else {
+ return LINUX_COMMAND;
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
index aaec9c6..932e3cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
@@ -104,8 +104,9 @@ public final class ByteArrayBuffer extends Buffer {
@Override
public void compact() {
- if (available() > 0) {
- System.arraycopy(data, rpos, data, 0, wpos - rpos);
+ int avail = available();
+ if (avail > 0) {
+ System.arraycopy(data, rpos, data, 0, avail);
}
wpos -= rpos;
rpos = 0;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
new file mode 100644
index 0000000..ad7e2ed
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java
@@ -0,0 +1,88 @@
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@code /dev/null} input stream
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NullInputStream extends InputStream implements Channel {
+ private final AtomicBoolean open = new AtomicBoolean(true);
+
+ public NullInputStream() {
+ super();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for reading one value");
+ }
+ return -1;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for reading " + len + " bytes");
+ }
+ return -1;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for skipping " + n + " bytes");
+ }
+ return 0L;
+ }
+
+ @Override
+ public int available() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for availability query");
+ }
+ return 0;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for reset");
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (open.getAndSet(false)) {
+ return; // debug breakpoint
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
new file mode 100644
index 0000000..64d59b6
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java
@@ -0,0 +1,70 @@
+/*
+ * 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.channels.Channel;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {code /dev/null} output stream
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class NullOutputStream extends OutputStream implements Channel {
+ private final AtomicBoolean open = new AtomicBoolean(true);
+
+ public NullOutputStream() {
+ super();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for writing one byte");
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for writing " + len + " bytes");
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (!isOpen()) {
+ throw new IOException("Stream is closed for flushing");
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (open.getAndSet(false)) {
+ return; // debug breakpoint
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/Command.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/Command.java b/sshd-core/src/main/java/org/apache/sshd/server/Command.java
index 380abe0..4043e48 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/Command.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/Command.java
@@ -18,7 +18,6 @@
*/
package org.apache.sshd.server;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -34,7 +33,7 @@ import java.io.OutputStream;
* </p>
* see {@link org.apache.sshd.server.shell.InvertedShellWrapper}.
*/
-public interface Command {
+public interface Command extends CommandLifecycle {
/**
* Set the input stream that can be used by the shell to read input.
@@ -63,28 +62,4 @@ public interface Command {
* @param callback The {@link ExitCallback} to call when shell is closed
*/
void setExitCallback(ExitCallback callback);
-
- /**
- * Starts the shell.
- * All streams must have been set before calling this method.
- * The command should implement Runnable, and this method
- * should spawn a new thread like:
- * <pre>
- * {@code
- * Thread(this).start();
- * }
- * </pre>
- *
- * @param env The {@link Environment}
- * @throws IOException If failed to start
- */
- void start(Environment env) throws IOException;
-
- /**
- * Destroy the shell.
- * This method can be called by the SSH server to destroy the shell because
- * the client has disconnected somehow.
- */
- void destroy();
-
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/CommandLifecycle.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/CommandLifecycle.java b/sshd-core/src/main/java/org/apache/sshd/server/CommandLifecycle.java
new file mode 100644
index 0000000..cbe3cf0
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/server/CommandLifecycle.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.server;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface CommandLifecycle {
+ /**
+ * Starts the command execution. All streams must have been set <U>before</U>
+ * calling this method. The command should implement {@link java.lang.Runnable},
+ * and this method should spawn a new thread like:
+ * <pre>
+ * {@code Thread(this).start(); }
+ * </pre>
+ *
+ * @param env The {@link Environment}
+ * @throws IOException If failed to start
+ */
+ void start(Environment env) throws IOException;
+
+ /**
+ * This method is called by the SSH server to destroy the command because
+ * the client has disconnected somehow.
+ *
+ * @throws Exception if failed to destroy
+ */
+ void destroy() throws Exception;
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index 55d5a5a..6a50428 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -475,11 +475,11 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("key.ser")));
}
- sshd.setShellFactory(new InteractiveProcessShellFactory());
+ sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
@Override
public boolean authenticate(String username, String password, ServerSession session) {
- return (username != null) && username.equals(password);
+ return (GenericUtils.length(username) > 0) && username.equals(password);
}
});
sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index 31cd5c1..fd4ff86 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -20,14 +20,9 @@ package org.apache.sshd.server.channel;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.TimerTask;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -66,7 +61,7 @@ import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.Signal;
-import org.apache.sshd.server.SignalListener;
+import org.apache.sshd.server.StandardEnvironment;
import org.apache.sshd.server.forward.ForwardingFilter;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.x11.X11ForwardSupport;
@@ -78,112 +73,6 @@ import org.apache.sshd.server.x11.X11ForwardSupport;
*/
public class ChannelSession extends AbstractServerChannel {
- protected static class StandardEnvironment implements Environment {
-
- private final Map<Signal, Set<SignalListener>> listeners;
- private final Map<String, String> env;
- private final Map<PtyMode, Integer> ptyModes;
-
- public StandardEnvironment() {
- listeners = new ConcurrentHashMap<>(3);
- env = new ConcurrentHashMap<>();
- ptyModes = new ConcurrentHashMap<>();
- }
-
- @Override
- public void addSignalListener(SignalListener listener, Signal... signals) {
- if (signals == null) {
- throw new IllegalArgumentException("signals may not be null");
- }
-
- addSignalListener(listener, Arrays.asList(signals));
- }
-
- @Override
- public void addSignalListener(SignalListener listener) {
- addSignalListener(listener, Signal.SIGNALS);
- }
-
- /*
- * NOTE: we don't care if the collection is a Set or not - after all,
- * we hold the listeners inside a Set, so even if we add several times
- * the same listener to the same signal set, it is harmless
- */
- @Override
- public void addSignalListener(SignalListener listener, Collection<Signal> signals) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
-
- if (signals == null) {
- throw new IllegalArgumentException("signals may not be null");
- }
-
- for (Signal s : signals) {
- getSignalListeners(s, true).add(listener);
- }
- }
-
- @Override
- public Map<String, String> getEnv() {
- return env;
- }
-
- @Override
- public Map<PtyMode, Integer> getPtyModes() {
- return ptyModes;
- }
-
- @Override
- public void removeSignalListener(SignalListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener may not be null");
- }
- for (Signal s : Signal.SIGNALS) {
- final Set<SignalListener> ls = getSignalListeners(s, false);
- if (ls != null) {
- ls.remove(listener);
- }
- }
- }
-
- public void signal(Signal signal) {
- final Set<SignalListener> ls = getSignalListeners(signal, false);
- if (ls != null) {
- for (SignalListener l : ls) {
- l.signal(signal);
- }
- }
- }
-
- /**
- * adds a variable to the environment. This method is called <code>set</code>
- * according to the name of the appropriate posix command <code>set</code>
- *
- * @param key environment variable name
- * @param value environment variable value
- */
- public void set(String key, String value) {
- // TODO: listening for property changes would be nice too.
- getEnv().put(key, value);
- }
-
- protected Set<SignalListener> getSignalListeners(Signal signal, boolean create) {
- Set<SignalListener> ls = listeners.get(signal);
- if (ls == null && create) {
- synchronized (listeners) {
- ls = listeners.get(signal);
- if (ls == null) {
- ls = new CopyOnWriteArraySet<>();
- listeners.put(signal, ls);
- }
- }
- }
- // may be null in case create=false
- return ls;
- }
- }
-
protected String type;
protected ChannelAsyncOutputStream asyncOut;
protected ChannelAsyncOutputStream asyncErr;
@@ -191,8 +80,8 @@ public class ChannelSession extends AbstractServerChannel {
protected OutputStream err;
protected Command command;
protected ChannelDataReceiver receiver;
- protected StandardEnvironment env = new StandardEnvironment();
protected Buffer tempBuffer;
+ protected final StandardEnvironment env = new StandardEnvironment();
protected final CloseFuture commandExitFuture = new DefaultCloseFuture(lock);
public ChannelSession() {
@@ -232,7 +121,14 @@ public class ChannelSession extends AbstractServerChannel {
if (immediately || command == null) {
commandExitFuture.setClosed();
} else if (!commandExitFuture.isClosed()) {
- IoUtils.closeQuietly(receiver);
+ IOException e = IoUtils.closeQuietly(receiver);
+ if (e != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("close({})[immediately={}] failed ({}) to close receiver: {}",
+ this, immediately, e.getClass().getSimpleName(), e.getMessage());
+ }
+ }
+
final TimerTask task = new TimerTask() {
@Override
public void run() {
@@ -265,17 +161,47 @@ public class ChannelSession extends AbstractServerChannel {
@Override
protected void doCloseImmediately() {
if (command != null) {
- command.destroy();
- command = null;
+ try {
+ command.destroy();
+ } catch (Exception e) {
+ log.warn("doCloseImmediately({}) failed ({}) to destroy command: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ } finally {
+ command = null;
+ }
}
- IoUtils.closeQuietly(remoteWindow, out, err, receiver);
+
+ IOException e = IoUtils.closeQuietly(remoteWindow, out, err, receiver);
+ if (e != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("doCloseImmediately({}) failed ({}) to close resources: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ }
+
+ if (log.isTraceEnabled()) {
+ Throwable[] suppressed = e.getSuppressed();
+ if (GenericUtils.length(suppressed) > 0) {
+ for (Throwable t : suppressed) {
+ log.trace("Suppressed " + t.getClass().getSimpleName() + ") while close immediately resource(s) of " + this + ": " + t.getMessage());
+ }
+ }
+ }
+ }
+
super.doCloseImmediately();
}
@Override
public void handleEof() throws IOException {
super.handleEof();
- IoUtils.closeQuietly(receiver);
+
+ IOException e = IoUtils.closeQuietly(receiver);
+ if (e != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("handleEof({}) failed ({}) to close receiver: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ }
+ }
}
@Override
@@ -371,7 +297,8 @@ public class ChannelSession extends AbstractServerChannel {
byte[] modes = buffer.getBytes();
Environment environment = getEnvironment();
Map<PtyMode, Integer> ptyModes = environment.getPtyModes();
- for (int i = 0; i < modes.length && modes[i] != 0;) {
+
+ for (int i = 0; i < modes.length && (modes[i] != PtyMode.TTY_OP_END);) {
int opcode = modes[i++] & 0x00FF;
PtyMode mode = PtyMode.fromInt(opcode);
/**
@@ -379,7 +306,7 @@ public class ChannelSession extends AbstractServerChannel {
* "Opcodes 160 to 255 are not yet defined, and cause parsing to stop"
*/
if (mode == null) {
- log.warn("Unknown pty opcode value: " + opcode);
+ log.warn("handlePtyReq(" + this + ") unknown pty opcode value: " + opcode);
break;
}
int val = ((modes[i++] << 24) & 0xff000000)
@@ -388,10 +315,10 @@ public class ChannelSession extends AbstractServerChannel {
| ((modes[i++]) & 0x000000ff);
ptyModes.put(mode, val);
}
+
if (log.isDebugEnabled()) {
- log.debug("pty for channel {}: term={}, size=({} - {}), pixels=({}, {}), modes=[{}]",
- id, term, tColumns, tRows,
- tWidth, tHeight, ptyModes);
+ log.debug("handlePtyReq({}): term={}, size=({} - {}), pixels=({}, {}), modes=[{}]",
+ this, term, tColumns, tRows, tWidth, tHeight, ptyModes);
}
addEnvVariable(Environment.ENV_TERM, term);
@@ -406,12 +333,11 @@ public class ChannelSession extends AbstractServerChannel {
int tWidth = buffer.getInt();
int tHeight = buffer.getInt();
if (log.isDebugEnabled()) {
- log.debug("window-change for channel {}: ({} - {}), ({}, {})",
- id, tColumns, tRows,
- tWidth, tHeight);
+ log.debug("handleWindowChange({}): ({} - {}), ({}, {})",
+ this, tColumns, tRows, tWidth, tHeight);
}
- final StandardEnvironment e = getEnvironment();
+ StandardEnvironment e = getEnvironment();
e.set(Environment.ENV_COLUMNS, Integer.toString(tColumns));
e.set(Environment.ENV_LINES, Integer.toString(tRows));
e.signal(Signal.WINCH);
@@ -421,14 +347,14 @@ public class ChannelSession extends AbstractServerChannel {
protected boolean handleSignal(Buffer buffer) throws IOException {
String name = buffer.getString();
if (log.isDebugEnabled()) {
- log.debug("Signal received on channel {}: {}", id, name);
+ log.debug("handleSignal({}): {}", this, name);
}
- final Signal signal = Signal.get(name);
+ Signal signal = Signal.get(name);
if (signal != null) {
getEnvironment().signal(signal);
} else {
- log.warn("Unknown signal received: " + name);
+ log.warn("handleSignal(" + this + ") unknown signal received: " + name);
}
return true;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
index 7b4d7e1..a0ebee2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InteractiveProcessShellFactory.java
@@ -19,6 +19,8 @@
package org.apache.sshd.server.shell;
+import java.util.List;
+
import org.apache.sshd.common.util.OsUtils;
/**
@@ -27,33 +29,14 @@ import org.apache.sshd.common.util.OsUtils;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class InteractiveProcessShellFactory extends ProcessShellFactory {
-
- public static final InteractiveProcessShellFactory INSTANCE;
-
- private static final String[] LINUX_COMMAND;
- private static final String[] WINDOWS_COMMAND;
-
- static {
- LINUX_COMMAND = new String[] {"/bin/sh", "-i", "-l"};
- WINDOWS_COMMAND = new String[] {"cmd.exe"};
- INSTANCE = new InteractiveProcessShellFactory();
- }
+ public static final InteractiveProcessShellFactory INSTANCE = new InteractiveProcessShellFactory();
public InteractiveProcessShellFactory() {
- super(resolveDefaultInteractiveCommand(), TtyOptions.resolveDefaultTtyOptions());
- }
-
- public static String[] resolveDefaultInteractiveCommand() {
- return resolveInteractiveCommand(OsUtils.isWin32());
+ super(OsUtils.resolveDefaultInteractiveCommand());
}
- public static String[] resolveInteractiveCommand(boolean isWin32) {
- // return clone(s) to avoid inadvertent modification
- if (isWin32) {
- return WINDOWS_COMMAND.clone();
- } else {
- return LINUX_COMMAND.clone();
- }
+ @Override
+ protected List<String> resolveEffectiveCommand(List<String> original) {
+ return original;
}
-
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
index aef00c1..ac3e8a7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
@@ -18,10 +18,10 @@
*/
package org.apache.sshd.server.shell;
-import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.Map;
+
+import org.apache.sshd.server.CommandLifecycle;
/**
* This shell have inverted streams, such as the one obtained when launching a
@@ -31,17 +31,7 @@ import java.util.Map;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public interface InvertedShell {
-
- /**
- * Starts the shell and will make the streams available for
- * the ssh server to retrieve and use.
- *
- * @param env The environment
- * @throws IOException if failed to start
- */
- void start(Map<String, String> env) throws IOException;
-
+public interface InvertedShell extends CommandLifecycle {
/**
* Returns the output stream used to feed the shell.
* This method is called after the shell has been started.
@@ -74,11 +64,4 @@ public interface InvertedShell {
* @return the exit value of the shell
*/
int exitValue();
-
- /**
- * Destroy the shell.
- * This method can be called by the SSH server to destroy the shell because
- * the client has disconnected somehow.
- */
- void destroy();
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
index 565ff95..6c55025 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -147,7 +148,7 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
@Override
public synchronized void start(Environment env) throws IOException {
// TODO propagate the Environment itself and support signal sending.
- shell.start(env.getEnv());
+ shell.start(env);
shellIn = shell.getInputStream();
shellOut = shell.getOutputStream();
shellErr = shell.getErrorStream();
@@ -160,10 +161,28 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
}
@Override
- public synchronized void destroy() {
- shell.destroy();
+ public synchronized void destroy() throws Exception {
+ Exception err = null;
+ try {
+ shell.destroy();
+ } catch (Exception e) {
+ log.warn("destroy({}) failed ({}) to destroy shell: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ err = GenericUtils.accumulateException(err, e);
+ }
+
if (shutdownExecutor && (executor instanceof ExecutorService)) {
- ((ExecutorService) executor).shutdown();
+ try {
+ ((ExecutorService) executor).shutdown();
+ } catch (Exception e) {
+ log.warn("destroy({}) failed ({}) to shut down executor: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ err = GenericUtils.accumulateException(err, e);
+ }
+ }
+
+ if (err != null) {
+ throw err;
}
}
@@ -198,7 +217,13 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
Thread.sleep(pumpSleepTime);
}
} catch (Exception e) {
- shell.destroy();
+ try {
+ shell.destroy();
+ } catch (Exception err) {
+ log.warn("pumpStreams({}) failed ({}) to destroy shell: {}",
+ this, e.getClass().getSimpleName(), e.getMessage());
+ err = GenericUtils.accumulateException(err, e);
+ }
int exitValue = shell.exitValue();
if (log.isDebugEnabled()) {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
index b964506..fa69429 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
@@ -21,14 +21,19 @@ package org.apache.sshd.server.shell;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
-import java.util.Set;
+import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.server.Environment;
/**
* Bridges the I/O streams between the SSH command and the process that executes it
@@ -36,38 +41,43 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
- private final Set<TtyOptions> ttyOptions;
- private final String[] command;
+ private final List<String> command;
private String cmdValue;
private Process process;
private TtyFilterOutputStream in;
private TtyFilterInputStream out;
private TtyFilterInputStream err;
- public ProcessShell(Collection<TtyOptions> ttyOptions, String ... command) {
- // we create a copy of the options so as to avoid concurrent modifications
- this.ttyOptions = GenericUtils.of(ttyOptions);
- // we clone the original array so as not to change it
- this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No process shell command(s)").clone();
+ /**
+ * @param command The command components which when joined (with space separator)
+ * create the full command to be executed by the shell
+ */
+ public ProcessShell(String ... command) {
+ this(GenericUtils.isEmpty(command) ? Collections.<String>emptyList() : Arrays.asList(command));
+ }
+ public ProcessShell(Collection<String> command) {
+ // we copy the original list so as not to change it
+ this.command = new ArrayList<String>(ValidateUtils.checkNotNullAndNotEmpty(command, "No process shell command(s)"));
this.cmdValue = GenericUtils.join(command, ' ');
}
@Override
- public void start(Map<String, String> env) throws IOException {
- for (int i = 0; i < command.length; i++) {
- String cmd = command[i];
+ public void start(Environment env) throws IOException {
+ Map<String, String> varsMap = resolveShellEnvironment(env.getEnv());
+ for (int i = 0; i < command.size(); i++) {
+ String cmd = command.get(i);
if ("$USER".equals(cmd)) {
- cmd = env.get("USER");
- command[i] = cmd;
+ cmd = varsMap.get("USER");
+ command.set(i, cmd);
cmdValue = GenericUtils.join(command, ' ');
}
}
ProcessBuilder builder = new ProcessBuilder(command);
- if (GenericUtils.size(env) > 0) {
+ if (GenericUtils.size(varsMap) > 0) {
try {
Map<String, String> procEnv = builder.environment();
- procEnv.putAll(env);
+ procEnv.putAll(varsMap);
} catch (Exception e) {
log.warn("Could not set environment for command=" + cmdValue, e);
}
@@ -78,9 +88,20 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
}
process = builder.start();
- out = new TtyFilterInputStream(process.getInputStream(), ttyOptions);
- err = new TtyFilterInputStream(process.getErrorStream(), ttyOptions);
- in = new TtyFilterOutputStream(process.getOutputStream(), err, ttyOptions);
+
+ Map<PtyMode, ?> modes = resolveShellTtyOptions(env.getPtyModes());
+ out = new TtyFilterInputStream(process.getInputStream(), modes);
+ err = new TtyFilterInputStream(process.getErrorStream(), modes);
+ in = new TtyFilterOutputStream(process.getOutputStream(), err, modes);
+ }
+
+ protected Map<String, String> resolveShellEnvironment(Map<String, String> env) {
+ return env;
+ }
+
+ // for some reason these modes provide best results BOTH with Linux SSH client and PUTTY
+ protected Map<PtyMode, Integer> resolveShellTtyOptions(Map<PtyMode, Integer> modes) {
+ return modes;
}
@Override
@@ -134,7 +155,16 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
IOException e = IoUtils.closeQuietly(getInputStream(), getOutputStream(), getErrorStream());
if (e != null) {
if (log.isDebugEnabled()) {
- log.debug(e.getClass().getSimpleName() + " while destroy streams of '" + cmdValue + "': " + e.getMessage());
+ log.debug(e.getClass().getSimpleName() + " while destroy streams of '" + this + "': " + e.getMessage());
+ }
+
+ if (log.isTraceEnabled()) {
+ Throwable[] suppressed = e.getSuppressed();
+ if (GenericUtils.length(suppressed) > 0) {
+ for (Throwable t : suppressed) {
+ log.trace("Suppressed " + t.getClass().getSimpleName() + ") while destroy streams of '" + this + "': " + t.getMessage());
+ }
+ }
}
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
index b29c46b..cfadb50 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShellFactory.java
@@ -18,10 +18,13 @@
*/
package org.apache.sshd.server.shell;
-import java.util.Collection;
-import java.util.Set;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.server.Command;
@@ -32,28 +35,30 @@ import org.apache.sshd.server.Command;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ProcessShellFactory extends AbstractLoggingBean implements Factory<Command> {
- private String[] command;
- private final Set<TtyOptions> ttyOptions;
+ private List<String> command;
public ProcessShellFactory() {
- this(GenericUtils.EMPTY_STRING_ARRAY);
+ this(Collections.<String>emptyList());
}
- public ProcessShellFactory(String[] command) {
- this(command, TtyOptions.resolveDefaultTtyOptions());
+ public ProcessShellFactory(String ... command) {
+ this(GenericUtils.isEmpty(command) ? Collections.<String>emptyList() : Arrays.asList(command));
}
- public ProcessShellFactory(String[] command, Collection<TtyOptions> ttyOptions) {
- this.command = command;
- this.ttyOptions = GenericUtils.of(ttyOptions);
+ public ProcessShellFactory(List<String> command) {
+ this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No command");
}
- public String[] getCommand() {
+ public List<String> getCommand() {
return command;
}
- public void setCommand(String[] command) {
- this.command = command;
+ public void setCommand(String ... command) {
+ setCommand(GenericUtils.isEmpty(command) ? Collections.<String>emptyList() : Arrays.asList(command));
+ }
+
+ public void setCommand(List<String> command) {
+ this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "No command");
}
@Override
@@ -62,6 +67,25 @@ public class ProcessShellFactory extends AbstractLoggingBean implements Factory<
}
protected InvertedShell createInvertedShell() {
- return new ProcessShell(ttyOptions, getCommand());
+ return new ProcessShell(resolveEffectiveCommand(getCommand()));
+ }
+
+ protected List<String> resolveEffectiveCommand(List<String> original) {
+ if (!OsUtils.isWin32()) {
+ return original;
+ }
+
+ // Turns out that running a command with no arguments works just fine
+ if (GenericUtils.size(original) <= 1) {
+ return original;
+ }
+
+ // For windows create a "cmd.exe /C "..."" string
+ String cmdName = original.get(0);
+ if (OsUtils.WINDOWS_SHELL_COMMAND_NAME.equalsIgnoreCase(cmdName)) {
+ return original; // assume callers knows what they're doing
+ }
+
+ return Arrays.asList(OsUtils.WINDOWS_SHELL_COMMAND_NAME, "/C", GenericUtils.join(original, ' '));
}
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
index 3059c8c..840eca6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
@@ -18,14 +18,16 @@
*/
package org.apache.sshd.server.shell;
-import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.StreamCorruptedException;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
import java.util.Set;
+import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -37,18 +39,21 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class TtyFilterInputStream extends FilterInputStream {
- private final Set<TtyOptions> ttyOptions;
+ public static final Set<PtyMode> INPUT_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(PtyMode.ONLCR, PtyMode.OCRNL, PtyMode.ONLRET, PtyMode.ONOCR));
+
+ private final Set<PtyMode> ttyOptions;
private Buffer buffer = new ByteArrayBuffer(32);
private int lastChar = -1;
- public TtyFilterInputStream(InputStream in, Collection<TtyOptions> ttyOptions) {
+ public TtyFilterInputStream(InputStream in, Map<PtyMode, ?> modes) {
+ this(in, PtyMode.resolveEnabledOptions(modes, INPUT_OPTIONS));
+ }
+
+ public TtyFilterInputStream(InputStream in, Collection<PtyMode> ttyOptions) {
super(in);
// we create a copy of the options so as to avoid concurrent modifications
- this.ttyOptions = GenericUtils.of(ttyOptions);
-
- if (this.ttyOptions.contains(TtyOptions.LfOnlyInput) && this.ttyOptions.contains(TtyOptions.CrLfInput)) {
- throw new IllegalArgumentException("Ambiguous TTY options: " + ttyOptions);
- }
+ this.ttyOptions = GenericUtils.of(ttyOptions); // TODO validate non-conflicting options
}
public synchronized void write(int c) {
@@ -56,11 +61,15 @@ public class TtyFilterInputStream extends FilterInputStream {
}
public synchronized void write(byte[] buf, int off, int len) {
- buffer.putBytes(buf, off, len);
+ if (len == 1) {
+ write(buf[off] & 0xFF);
+ } else {
+ buffer.putBytes(buf, off, len);
+ }
}
@Override
- public int available() throws IOException {
+ public synchronized int available() throws IOException {
return super.available() + buffer.available();
}
@@ -71,55 +80,87 @@ public class TtyFilterInputStream extends FilterInputStream {
return c;
}
- if ((c == '\r') && ttyOptions.contains(TtyOptions.LfOnlyInput)) {
- c = readRawInput();
- if (c == -1) {
- throw new EOFException("Premature EOF while waiting for LF after CR");
- }
-
- if (c != '\n') {
- throw new StreamCorruptedException("CR not followed by LF");
- }
- }
-
- if ((c == '\n') && ttyOptions.contains(TtyOptions.CrLfInput)) {
- if (lastChar != '\r') {
- c = '\r';
- Buffer buf = new ByteArrayBuffer();
- buf.putByte((byte) '\n');
- buf.putBuffer(buffer);
- buffer = buf;
- }
+ if (c == '\r') {
+ c = handleCR();
+ } else if (c == '\n') {
+ c = handleLF();
}
lastChar = c;
return c;
}
+ protected int handleCR() throws IOException {
+ if (ttyOptions.contains(PtyMode.OCRNL)) {
+ return '\n'; // Translate carriage return to newline
+ } else {
+ return '\r';
+ }
+ }
+
+ protected int handleLF() throws IOException {
+ // Map NL to CR-NL.
+ if ((ttyOptions.contains(PtyMode.ONLCR) || ttyOptions.contains(PtyMode.ONOCR)) && (lastChar != '\r')) {
+ buffer = insertCharacter(buffer, '\n');
+ return '\r';
+ } else if (ttyOptions.contains(PtyMode.ONLRET)) { // Newline performs a carriage return
+ return '\r';
+ } else {
+ return '\n';
+ }
+ }
+
+ // TODO add 'insertXXX' methods to the Buffer class
+ protected Buffer insertCharacter(Buffer org, int c) {
+ int remaining = org.capacity();
+ int readPos = org.rpos();
+ // see if can accommodate the character in the original buffer
+ if ((remaining > 0) && (readPos > 0)) {
+ int writePos = org.wpos();
+ org.wpos(readPos - 1);
+ org.putByte((byte) c);
+ org.wpos(writePos);
+ org.rpos(readPos - 1);
+ return org;
+ } else {
+ Buffer buf = new ByteArrayBuffer(org.available() + 1);
+ buf.putByte((byte) c);
+ buf.putBuffer(org);
+ return buf;
+ }
+
+ }
protected int readRawInput() throws IOException {
- // see if have any pending data
if (buffer.available() > 0) {
- int c = buffer.getUByte();
- buffer.compact();
- return c;
+ return buffer.getUByte();
+ } else {
+ return this.in.read();
}
-
- return super.read();
}
@Override
public synchronized int read(byte[] b, int off, int len) throws IOException {
+ if (len == 1) {
+ int c = read();
+ if (c == -1) {
+ return -1;
+ }
+
+ b[off] = (byte) c;
+ return 1;
+ }
+
if (buffer.available() == 0) {
- int nb = super.read(b, off, len);
+ int nb = this.in.read(b, off, len);
if (nb == -1) {
- return -1;
+ return nb;
}
buffer.putRawBytes(b, off, nb);
}
int nb = 0;
- while ((nb < len) && (buffer.available() > 0)) {
- b[off + nb++] = (byte) read();
+ for (int curPos = off; (nb < len) && (buffer.available() > 0); nb++, curPos++) {
+ b[curPos] = (byte) read();
}
return nb;
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterOutputStream.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterOutputStream.java
index 010eb28..f4c4988 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterOutputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterOutputStream.java
@@ -22,59 +22,105 @@ import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
import java.util.Set;
+import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
/**
* Handles the output stream while taking care of the {@link TtyOptions} for CR / LF
- * and ECHO settings. <B>Note:</B> does not close the echo stream when filter stream is closed
+ * and ECHO settings
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class TtyFilterOutputStream extends FilterOutputStream {
- private final Set<TtyOptions> ttyOptions;
- private TtyFilterInputStream echo;
- private int lastChar = -1;
+ public static final Set<PtyMode> OUTPUT_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(PtyMode.ECHO, PtyMode.INLCR, PtyMode.ICRNL, PtyMode.IGNCR));
- public TtyFilterOutputStream(OutputStream out, TtyFilterInputStream echo, Collection<TtyOptions> ttyOptions) {
+ private final Set<PtyMode> ttyOptions;
+ private final TtyFilterInputStream echo;
+
+ public TtyFilterOutputStream(OutputStream out, TtyFilterInputStream echo, Map<PtyMode, ?> modes) {
+ this(out, echo, PtyMode.resolveEnabledOptions(modes, OUTPUT_OPTIONS));
+ }
+
+ public TtyFilterOutputStream(OutputStream out, TtyFilterInputStream echo, Collection<PtyMode> ttyOptions) {
super(out);
// we create a copy of the options so as to avoid concurrent modifications
- this.ttyOptions = GenericUtils.of(ttyOptions);
- if (this.ttyOptions.contains(TtyOptions.LfOnlyOutput) && this.ttyOptions.contains(TtyOptions.CrLfOutput)) {
- throw new IllegalArgumentException("Ambiguous TTY options: " + this.ttyOptions);
- }
-
- this.echo = this.ttyOptions.contains(TtyOptions.Echo) ? ValidateUtils.checkNotNull(echo, "No echo stream") : echo;
+ this.ttyOptions = GenericUtils.of(ttyOptions); // TODO validate non-conflicting options
+ this.echo = this.ttyOptions.contains(PtyMode.ECHO) ? ValidateUtils.checkNotNull(echo, "No echo stream") : echo;
}
@Override
public void write(int c) throws IOException {
- if ((c == '\r') && ttyOptions.contains(TtyOptions.LfOnlyOutput)) {
- lastChar = c;
- return;
+ if (c == '\r') {
+ handleCR();
+ } else if (c == '\n') {
+ handleLF();
+ } else {
+ writeRawOutput(c);
}
+ }
- if ((c == '\n') && ttyOptions.contains(TtyOptions.CrLfOutput) && (lastChar != '\r')) {
+ protected void handleCR() throws IOException {
+ if (ttyOptions.contains(PtyMode.ICRNL)) {
+ writeRawOutput('\n'); // Map CR to NL on input
+ } else if (ttyOptions.contains(PtyMode.IGNCR)) {
+ return; // Ignore CR on input
+ } else {
writeRawOutput('\r');
}
+ }
- writeRawOutput(c);
+ protected void handleLF() throws IOException {
+ if (ttyOptions.contains(PtyMode.INLCR)) {
+ writeRawOutput('\r'); // Map NL into CR on input
+ } else {
+ writeRawOutput('\n');
+ }
}
protected void writeRawOutput(int c) throws IOException {
- lastChar = c;
- super.write(c);
- if (ttyOptions.contains(TtyOptions.Echo)) {
+ this.out.write(c);
+ if (ttyOptions.contains(PtyMode.ECHO)) {
echo.write(c);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
- for (int curPos = off, l = 0; l < len; curPos++, l++) {
- write(b[curPos]);
+ if (len == 1) {
+ write(b[off] & 0xFF);
+ return;
+ }
+
+ int lastPos = 0;
+ int maxPos = off + len;
+ for (int curPos = off; curPos < maxPos; curPos++) {
+ int c = b[curPos] & 0xFF;
+ if ((c == '\r') || (c == '\n')) {
+ if (lastPos < curPos) { // No CR or LF in this segment
+ writeRawOutput(b, lastPos, curPos - lastPos);
+ }
+
+ lastPos = curPos + 1; // prepare for next character
+ write(c);
+ }
+ }
+
+ if (lastPos < maxPos) { // No CR or LF in this segment
+ writeRawOutput(b, lastPos, maxPos - lastPos);
+ }
+ }
+
+ protected void writeRawOutput(byte[] b, int off, int len) throws IOException {
+ this.out.write(b, off, len);
+ if (ttyOptions.contains(PtyMode.ECHO)) {
+ echo.write(b, off, len);
}
}
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyOptions.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyOptions.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyOptions.java
deleted file mode 100644
index d0192e8..0000000
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyOptions.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sshd.server.shell;
-
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.Set;
-
-import org.apache.sshd.common.util.OsUtils;
-
-/**
- * Options controlling the I/O streams behavior regarding CR/LF and echoing
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public enum TtyOptions {
- /**
- * Echo the output
- */
- Echo,
- /**
- * Convert CRLF to LF if not already such when writing to output stream
- */
- LfOnlyOutput,
- /**
- * Convert LF to CRLF if not already such when writing to output stream
- */
- CrLfOutput,
- /**
- * Convert LF to CRLF if not already such when reading from input stream
- */
- CrLfInput,
- /**
- * Convert CRLF to LF if not already such when reading from input stream
- */
- LfOnlyInput;
-
- public static final Set<TtyOptions> LINUX_OPTIONS =
- Collections.unmodifiableSet(EnumSet.of(TtyOptions.LfOnlyOutput, TtyOptions.LfOnlyInput));
-
- /*
- * NOTE !!! the assumption is that a Windows server is writing to a Linux
- * client (the most likely) thus it expects CRLF on input, but outputs LF only
- */
- public static final Set<TtyOptions> WINDOWS_OPTIONS =
- Collections.unmodifiableSet(EnumSet.of(TtyOptions.Echo, TtyOptions.LfOnlyOutput, TtyOptions.CrLfInput));
-
- public static Set<TtyOptions> resolveDefaultTtyOptions() {
- return resolveTtyOptions(OsUtils.isWin32());
- }
-
- public static Set<TtyOptions> resolveTtyOptions(boolean isWin32) {
- if (isWin32) {
- return WINDOWS_OPTIONS;
- } else {
- return LINUX_OPTIONS;
- }
- }
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java b/sshd-core/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
new file mode 100644
index 0000000..a61f924
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/common/channel/PtyModeTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.channel;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class PtyModeTest extends BaseTestSupport {
+ public PtyModeTest() {
+ super();
+ }
+
+ @Test
+ public void testFromInt() {
+ for (PtyMode expected : PtyMode.MODES) {
+ int num = expected.toInt();
+ PtyMode actual = PtyMode.fromInt(num);
+ assertSame("Mismatched result for value=" + num, expected, actual);
+ }
+ }
+
+ @Test
+ public void testEnabledOptions() {
+ Set<PtyMode> expected = EnumSet.of(PtyMode.ECHO, PtyMode.CS8, PtyMode.ICANON);
+ Map<PtyMode, Integer> modes = PtyMode.createEnabledOptions(expected);
+ assertEquals("Mismatched modes size", expected.size(), GenericUtils.size(modes));
+
+ for (PtyMode m : expected) {
+ assertSame("Mismatched setting for " + m, PtyMode.TRUE_SETTING, modes.get(m));
+ }
+
+ Set<PtyMode> actual = PtyMode.resolveEnabledOptions(modes, expected);
+ assertEquals("Mismatched enabled options", expected, actual);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/test/java/org/apache/sshd/server/channel/ChannelSessionTest.java
----------------------------------------------------------------------
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 3c86a32..97c5cc3 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
@@ -19,7 +19,6 @@
package org.apache.sshd.server.channel;
import java.io.IOException;
-import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -27,8 +26,6 @@ import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.channel.ChannelAsyncOutputStream;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.server.Signal;
-import org.apache.sshd.server.SignalListener;
import org.apache.sshd.util.test.BaseTestSupport;
import org.apache.sshd.util.test.BogusChannel;
import org.junit.FixMethodOrder;
@@ -66,23 +63,4 @@ public class ChannelSessionTest extends BaseTestSupport {
assertTrue("Expanded ?", expanded.get());
}
}
-
- @Test
- public void testAddSignalListenerOnDuplicateSignals() {
- ChannelSession.StandardEnvironment environ = new ChannelSession.StandardEnvironment();
- SignalListener listener = new SignalListener() {
- @Override
- public void signal(Signal signal) {
- // ignored
- }
- };
-
- for (Signal s : Signal.SIGNALS) {
- environ.addSignalListener(listener, s, s, s, s, s, s);
-
- Collection<SignalListener> ls = environ.getSignalListeners(s, false);
- int numListeners = (ls == null) ? 0 : ls.size();
- assertEquals("Mismatched registered listeners count for signal=" + s, 1, numListeners);
- }
- }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
index e74cb08..6632965 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
@@ -25,8 +25,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
-import java.util.Map;
+import org.apache.sshd.server.Environment;
import org.apache.sshd.util.test.BaseTestSupport;
import org.apache.sshd.util.test.BogusEnvironment;
import org.apache.sshd.util.test.BogusExitCallback;
@@ -80,7 +80,7 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
private boolean destroyed;
@Override
- public void start(Map<String, String> env) throws IOException {
+ public void start(Environment env) throws IOException {
bogusShell.start(env);
}
@@ -172,7 +172,7 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
InvertedShell shell = new InvertedShell() {
@Override
- public void start(Map<String, String> env) throws IOException {
+ public void start(Environment env) throws IOException {
// ignored
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/97b73947/sshd-core/src/test/java/org/apache/sshd/server/shell/TtyFilterInputStreamTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/shell/TtyFilterInputStreamTest.java b/sshd-core/src/test/java/org/apache/sshd/server/shell/TtyFilterInputStreamTest.java
index e3b1e56..b819984 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/shell/TtyFilterInputStreamTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/shell/TtyFilterInputStreamTest.java
@@ -19,105 +19,114 @@
package org.apache.sshd.server.shell;
-import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.EnumSet;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.util.test.BaseTestSupport;
import org.junit.FixMethodOrder;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
public class TtyFilterInputStreamTest extends BaseTestSupport {
- public TtyFilterInputStreamTest() {
- super();
- }
+ private static final List<PtyMode> MODES =
+ Collections.unmodifiableList(new ArrayList<PtyMode>(TtyFilterInputStream.INPUT_OPTIONS.size() + 2) {
+ private static final long serialVersionUID = 1L; // we-re not serializing it
- @Test
- public void testLfOnlyStream() throws IOException {
- List<String> expected = createTestLines();
- List<String> actual = new ArrayList<>(expected.size());
- byte[] data = GenericUtils.join(expected, "\r\n").getBytes(StandardCharsets.UTF_8);
- try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
- TtyFilterInputStream tty = new TtyFilterInputStream(bais, Collections.singleton(TtyOptions.LfOnlyInput)) {
- private long offset;
-
- @Override
- public synchronized int read() throws IOException {
- int c = super.read();
- if (c == -1) {
- return -1;
- }
-
- offset++;
- if (c == '\n') {
- offset++; // compensate for CR filtering
- }
- assertFalse("Unexpected CR at offset=" + offset, c == '\r');
- return c;
- }
- };
- BufferedReader rdr = new BufferedReader(new InputStreamReader(tty, StandardCharsets.UTF_8))) {
-
- for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
- actual.add(line);
- }
- }
+ {
+ add(PtyMode.ECHO); // NO-OP
+
+ for (PtyMode m : TtyFilterInputStream.INPUT_OPTIONS) {
+ add(m);
+ }
+ }
+ });
- assertListEquals("Mismatched lines", expected, actual);
+ private final PtyMode mode;
+
+ public TtyFilterInputStreamTest(PtyMode mode) {
+ this.mode = ValidateUtils.checkNotNull(mode, "No test modes");
+ }
+
+ @Parameters(name = "mode={0}")
+ public static Collection<Object[]> parameters() {
+ return parameterize(MODES);
}
@Test
- public void testCrLfStream() throws IOException {
- List<String> expected = createTestLines();
- List<String> actual = new ArrayList<>(expected.size());
- byte[] data = GenericUtils.join(expected, '\n').getBytes(StandardCharsets.UTF_8);
- try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
- TtyFilterInputStream tty = new TtyFilterInputStream(bais, Collections.singleton(TtyOptions.CrLfInput)) {
- private long offset;
- private int lastChar = -1;
-
- @Override
- public synchronized int read() throws IOException {
- int c = super.read();
- if (c == -1) {
- return -1;
- }
-
- if (c != '\r') {
- offset++;
- }
-
- if (c == '\n') {
- assertEquals("LF not preceded by CR at offset=" + offset, '\r', lastChar);
- }
-
- lastChar = c;
- return c;
- }
- };
- BufferedReader rdr = new BufferedReader(new InputStreamReader(tty, StandardCharsets.UTF_8))) {
-
- for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
- actual.add(line);
+ public void testCRLFHandling() throws IOException {
+ List<String> lines = Arrays.asList(getClass().getPackage().getName(),
+ getClass().getSimpleName(), getCurrentTestName(),
+ "(" + mode + ")", new Date(System.currentTimeMillis()).toString());
+ String content = GenericUtils.join(lines, "\r\n");
+ try (ByteArrayInputStream bais = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+ TtyFilterInputStream tty = new TtyFilterInputStream(bais, EnumSet.of(mode))) {
+ final AtomicInteger crCount = new AtomicInteger(0);
+ final AtomicInteger lfCount = new AtomicInteger(0);
+
+ try (OutputStream output = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ if (b == '\r') {
+ crCount.incrementAndGet();
+ } else if (b == '\n') {
+ lfCount.incrementAndGet();
+ }
+ }
+ }) {
+ long copySize = IoUtils.copy(tty, output);
+ assertTrue("Copy size (" + copySize + ") above total length (" + content.length() + ")", copySize <= content.length());
}
- }
- assertListEquals("Mismatched lines", expected, actual);
+ assertCRLFCounts(mode, lines.size() - 1 /* last line has no NL */, crCount.get(), lfCount.get());
+ }
}
- private List<String> createTestLines() {
- return Arrays.asList(getClass().getPackage().getName(), getClass().getSimpleName(), getCurrentTestName(), new Date(System.currentTimeMillis()).toString());
+ private static void assertCRLFCounts(PtyMode mode, int numLines, int crCount, int lfCount) {
+ switch(mode) {
+ case ECHO:
+ case ONLCR:
+ case ONOCR:
+ // No modifications
+ assertEquals("Mismatched CR count", numLines, crCount);
+ assertEquals("Mismatched LF count", numLines, lfCount);
+ break;
+
+ case OCRNL:
+ // Translate carriage return to newline
+ assertEquals("Mismatched CR count", 0, crCount);
+ assertEquals("Mismatched LF count", 2 * numLines, lfCount);
+ break;
+
+ case ONLRET:
+ // Newline performs a carriage return
+ assertEquals("Mismatched CR count", 2 * numLines, crCount);
+ assertEquals("Mismatched LF count", 0, lfCount);
+ break;
+
+ default:
+ fail("Unsupported mode: " + mode);
+ }
}
}