All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.termd.core.ssh.TtyCommand Maven / Gradle / Ivy

Go to download

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