io.termd.core.ssh.TtyCommand Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of termd-core Show documentation
Show all versions of termd-core Show documentation
An open source terminal daemon library providing terminal handling in Java,
back ported to Alibaba by core engine team to support running on JDK 6+.
The newest version!
/*
* Copyright 2015 Julien Viet
*
* Licensed 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 io.termd.core.ssh;
import io.termd.core.function.BiConsumer;
import io.termd.core.function.Consumer;
import io.termd.core.io.BinaryDecoder;
import io.termd.core.io.BinaryEncoder;
import io.termd.core.tty.TtyConnection;
import io.termd.core.tty.TtyConnectionSupport;
import io.termd.core.tty.TtyEvent;
import io.termd.core.tty.TtyEventDecoder;
import io.termd.core.tty.TtyOutputMode;
import io.termd.core.util.Vector;
import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.future.CloseFuture;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoInputStream;
import org.apache.sshd.common.io.IoOutputStream;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.server.AsyncCommand;
import org.apache.sshd.server.ChannelSessionAware;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.Signal;
import org.apache.sshd.server.SignalListener;
import org.apache.sshd.server.channel.ChannelDataReceiver;
import org.apache.sshd.server.channel.ChannelSession;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Julien Viet
*/
public class TtyCommand implements AsyncCommand, ChannelDataReceiver, ChannelSessionAware {
private static final Pattern LC_PATTERN = Pattern.compile("(?:\\p{Alpha}{2}_\\p{Alpha}{2}\\.)?([^@]+)(?:@.+)?");
private final Consumer handler;
private final Charset defaultCharset;
private Charset charset;
private String term;
private TtyEventDecoder eventDecoder;
private BinaryDecoder decoder;
private Consumer stdout;
private Consumer out;
private Vector size = null;
private Consumer sizeHandler;
private Consumer termHandler;
private Consumer closeHandler;
protected ChannelSession session;
private final AtomicBoolean closed = new AtomicBoolean();
private ExitCallback exitCallback;
private Connection conn;
private IoOutputStream ioOut;
private long lastAccessedTime = System.currentTimeMillis();
public TtyCommand(Charset defaultCharset, Consumer handler) {
this.handler = handler;
this.defaultCharset = defaultCharset;
}
@Override
public int data(ChannelSession channel, byte[] buf, int start, int len) throws IOException {
if (decoder != null) {
lastAccessedTime = System.currentTimeMillis();
decoder.write(buf, start, len);
} else {
// Data send too early ?
}
return len;
}
@Override
public void setChannelSession(ChannelSession session) {
this.session = session;
}
@Override
public void setInputStream(InputStream in) {
}
@Override
public void setOutputStream(final OutputStream out) {
}
@Override
public void setErrorStream(OutputStream err) {
}
@Override
public void setIoInputStream(IoInputStream in) {
}
@Override
public void setIoOutputStream(final IoOutputStream out) {
this.ioOut = out;
this.out = new Consumer() {
@Override
public void accept(byte[] bytes) {
out.write(new ByteArrayBuffer(bytes));
}
};
}
@Override
public void setIoErrorStream(IoOutputStream err) {
}
@Override
public void setExitCallback(ExitCallback callback) {
this.exitCallback = callback;
}
@Override
public void start(final Environment env) throws IOException {
String lcctype = env.getEnv().get("LC_CTYPE");
if (lcctype != null) {
charset = parseCharset(lcctype);
}
if (charset == null) {
charset = defaultCharset;
}
env.addSignalListener(new SignalListener() {
@Override
public void signal(Signal signal) {
updateSize(env);
}
}, EnumSet.of(org.apache.sshd.server.Signal.WINCH));
updateSize(env);
// Event handling
int vintr = getControlChar(env, PtyMode.VINTR, 3);
int vsusp = getControlChar(env, PtyMode.VSUSP, 26);
int veof = getControlChar(env, PtyMode.VEOF, 4);
//
eventDecoder = new TtyEventDecoder(vintr, vsusp, veof);
decoder = new BinaryDecoder(512, charset, eventDecoder);
stdout = new TtyOutputMode(new BinaryEncoder(charset, out));
term = env.getEnv().get("TERM");
conn = new Connection();
//
session.setDataReceiver(this);
handler.accept(conn);
}
private int getControlChar(Environment env, PtyMode key, int def) {
Integer controlChar = env.getPtyModes().get(key);
return controlChar != null ? controlChar : def;
}
public void updateSize(Environment env) {
String columns = env.getEnv().get(Environment.ENV_COLUMNS);
String lines = env.getEnv().get(Environment.ENV_LINES);
if (lines != null && columns != null) {
Vector size;
try {
int width = Integer.parseInt(columns);
int height = Integer.parseInt(lines);
size = new Vector(width, height);
}
catch (Exception ignore) {
size = null;
}
if (size != null) {
this.size = size;
if (sizeHandler != null) {
sizeHandler.accept(size);
}
}
}
}
@Override
public void close() throws IOException {
close(0);
}
private void close(final int exit) throws IOException {
ioOut.close(false).addListener(new SshFutureListener() {
@Override
public void operationComplete(CloseFuture future) {
exitCallback.onExit(exit);
if (closed.compareAndSet(false, true)) {
if (closeHandler != null) {
closeHandler.accept(null);
} else {
// This happen : report it to the SSHD project
}
}
}
});
}
@Override
public void destroy() {
// Test this
}
protected void execute(Runnable task) {
session.getSession().getFactoryManager().getScheduledExecutorService().execute(task);
}
protected void schedule(Runnable task, long delay, TimeUnit unit) {
session.getSession().getFactoryManager().getScheduledExecutorService().schedule(task, delay, unit);
}
private static Charset parseCharset(String value) {
Matcher matcher = LC_PATTERN.matcher(value);
if (matcher.matches()) {
try {
return Charset.forName(matcher.group(1));
}
catch (Exception ignore) {
}
}
return null;
}
private class Connection extends TtyConnectionSupport {
@Override
public Charset inputCharset() {
return charset;
}
@Override
public Charset outputCharset() {
return charset;
}
@Override
public long lastAccessedTime() {
return lastAccessedTime;
}
@Override
public String terminalType() {
return term;
}
@Override
public Consumer getStdinHandler() {
return eventDecoder.getReadHandler();
}
@Override
public void setStdinHandler(Consumer handler) {
eventDecoder.setReadHandler(handler);
}
@Override
public Consumer getTerminalTypeHandler() {
return termHandler;
}
@Override
public void setTerminalTypeHandler(Consumer handler) {
termHandler = handler;
}
@Override
public Vector size() {
return size;
}
@Override
public Consumer getSizeHandler() {
return sizeHandler;
}
@Override
public void setSizeHandler(Consumer handler) {
sizeHandler = handler;
}
@Override
public BiConsumer getEventHandler() {
return eventDecoder.getEventHandler();
}
@Override
public void setEventHandler(BiConsumer handler) {
eventDecoder.setEventHandler(handler);
}
@Override
public Consumer stdoutHandler() {
return stdout;
}
@Override
public void execute(Runnable task) {
TtyCommand.this.execute(task);
}
@Override
public void schedule(Runnable task, long delay, TimeUnit unit) {
TtyCommand.this.schedule(task, delay, unit);
}
@Override
public void setCloseHandler(Consumer handler) {
closeHandler = handler;
}
@Override
public Consumer getCloseHandler() {
return closeHandler;
}
@Override
public void close() {
try {
TtyCommand.this.close();
} catch (IOException ignore) {
}
}
@Override
public void close(int exit) {
try {
TtyCommand.this.close(exit);
} catch (IOException ignore) {
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy