org.aesh.terminal.ssh.TtyCommand Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2017 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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 org.aesh.terminal.ssh;
import org.aesh.io.Decoder;
import org.aesh.io.Encoder;
import org.aesh.terminal.Attributes;
import org.aesh.terminal.Connection;
import org.aesh.terminal.Device;
import org.aesh.terminal.EventDecoder;
import org.aesh.terminal.tty.Capability;
import org.aesh.terminal.tty.Signal;
import org.aesh.terminal.tty.Size;
import org.aesh.terminal.tty.TtyOutputMode;
import org.apache.sshd.common.channel.PtyMode;
import org.apache.sshd.common.io.IoInputStream;
import org.apache.sshd.common.io.IoOutputStream;
import org.apache.sshd.common.io.IoWriteFuture;
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.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.function.Consumer;
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 EventDecoder eventDecoder;
private Decoder decoder;
private Consumer stdout;
private Consumer out;
private Size size = null;
private Consumer sizeHandler;
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();
private Device device;
private IoWriteFuture writeFuture;
private Attributes attributes;
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(IoOutputStream out) {
this.ioOut = out;
this.out = (byte[] bytes) -> {
if(writeFuture == null)
writeFuture = out.write(new ByteArrayBuffer(bytes));
else if(writeFuture.isWritten())
writeFuture = out.write(new ByteArrayBuffer(bytes));
else {
try {
writeFuture.await();
writeFuture = out.write(new ByteArrayBuffer(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}
};
}
@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(signal -> updateSize(env), EnumSet.of(org.apache.sshd.server.Signal.WINCH));
updateSize(env);
// Event handling
int vintr = getControlChar(env, PtyMode.VINTR, 3);
int veof = getControlChar(env, PtyMode.VEOF, 4);
int vsusp = getControlChar(env, PtyMode.VSUSP, 26);
device = new SSHDevice(env.getEnv().get("TERM"));
attributes = SSHAttributesBuilder.builder().environment(env).build();
eventDecoder = new EventDecoder(attributes);
decoder = new Decoder(512, charset, eventDecoder);
stdout = new TtyOutputMode(new Encoder(charset, out));
conn = new SSHConnection();
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) {
Size size;
try {
int width = Integer.parseInt(columns);
int height = Integer.parseInt(lines);
size = new Size(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(int exit) throws IOException {
ioOut.close(false).addListener(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 SSHConnection implements Connection {
@Override
public Charset inputEncoding() {
return charset;
}
@Override
public Charset outputEncoding() {
return charset;
}
@Override
public boolean supportsAnsi() {
return true;
}
public long lastAccessedTime() {
return lastAccessedTime;
}
public Device device() {
return device;
}
@Override
public Consumer getStdinHandler() {
return eventDecoder.getInputHandler();
}
@Override
public void setStdinHandler(Consumer handler) {
eventDecoder.setInputHandler(handler);
}
@Override
public Size size() {
return size;
}
@Override
public Consumer getSizeHandler() {
return sizeHandler;
}
@Override
public void setSizeHandler(Consumer handler) {
sizeHandler = handler;
}
@Override
public Consumer getSignalHandler() {
return eventDecoder.getSignalHandler();
}
@Override
public void setSignalHandler(Consumer handler) {
eventDecoder.setSignalHandler(handler);
}
@Override
public Consumer stdoutHandler() {
return stdout;
}
@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) {
}
}
@Override
public void openBlocking() {
}
@Override
public void openNonBlocking() {
}
@Override
public boolean put(Capability capability, Object... params) {
return false;
}
@Override
public Attributes getAttributes() {
return attributes;
}
@Override
public void setAttributes(Attributes attr) {
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy