001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020package org.crsh.ssh.term;
021
022import org.crsh.term.CodeType;
023import org.crsh.term.spi.TermIO;
024import org.crsh.text.Style;
025
026import java.io.*;
027import java.util.concurrent.atomic.AtomicBoolean;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030
031public class SSHIO implements TermIO {
032
033  /** Copied from net.wimpi.telnetd.io.TerminalIO. */
034  private static final int UP = 1001;
035
036  /** Copied from net.wimpi.telnetd.io.TerminalIO. */
037  private static final int DOWN = 1002;
038
039  /** Copied from net.wimpi.telnetd.io.TerminalIO. */
040  private static final int RIGHT = 1003;
041
042  /** Copied from net.wimpi.telnetd.io.TerminalIO. */
043  private static final int LEFT = 1004;
044
045  /** Copied from net.wimpi.telnetd.io.TerminalIO. */
046  private static final int HANDLED = 1305;
047
048  /** . */
049  private static final int BACKWARD_WORD = -1;
050
051  /** . */
052  private static final int FORWARD_WORD = -2;
053
054  /** . */
055  private static final Logger log = Logger.getLogger(SSHIO.class.getName());
056
057  /** . */
058  private final Reader reader;
059
060  /** . */
061  private final Writer writer;
062
063  /** . */
064  private static final int STATUS_NORMAL = 0;
065
066  /** . */
067  private static final int STATUS_READ_ESC_1 = 1;
068
069  /** . */
070  private static final int STATUS_READ_ESC_2 = 2;
071
072  /** . */
073  private int status;
074
075  /** . */
076  private final CRaSHCommand command;
077
078  /** . */
079  final AtomicBoolean closed;
080
081  /** . */
082  private boolean useAlternate;
083
084  public SSHIO(CRaSHCommand command) {
085    this.command = command;
086    this.writer = new OutputStreamWriter(command.out);
087    this.reader = new InputStreamReader(command.in);
088    this.status = STATUS_NORMAL;
089    this.closed = new AtomicBoolean(false);
090    this.useAlternate = false;
091  }
092
093  public int read() throws IOException {
094    while (true) {
095      if (closed.get()) {
096        return HANDLED;
097      } else {
098        int r;
099        try {
100          r = reader.read();
101        } catch (IOException e) {
102          // This would likely happen when the client close the connection
103          // when we are blocked on a read operation by the
104          // CRaShCommand#destroy() method
105          close();
106          return HANDLED;
107        }
108        if (r == -1) {
109          return HANDLED;
110        } else {
111          switch (status) {
112            case STATUS_NORMAL:
113              if (r == 27) {
114                status = STATUS_READ_ESC_1;
115              } else {
116                return r;
117              }
118              break;
119            case STATUS_READ_ESC_1:
120              if (r == 91) {
121                status = STATUS_READ_ESC_2;
122              } else if (r == 98) {
123                status = STATUS_NORMAL;
124                return BACKWARD_WORD;
125              } else if (r == 102) {
126                status = STATUS_NORMAL;
127                return FORWARD_WORD;
128              } else {
129                status = STATUS_NORMAL;
130                log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC code");
131              }
132              break;
133            case STATUS_READ_ESC_2:
134              status = STATUS_NORMAL;
135              switch (r) {
136                case 65:
137                  return UP;
138                case 66:
139                  return DOWN;
140                case 67:
141                  return RIGHT;
142                case 68:
143                  return LEFT;
144                default:
145                  log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC+91 code");
146                  break;
147              }
148          }
149        }
150      }
151    }
152  }
153
154  public int getWidth() {
155    return command.getContext().getWidth();
156  }
157
158  public int getHeight() {
159    return command.getContext().getHeight();
160  }
161
162  public String getProperty(String name) {
163    return command.getContext().getProperty(name);
164  }
165
166  public boolean takeAlternateBuffer() throws IOException {
167    if (!useAlternate) {
168      useAlternate = true;
169      writer.write("\033[?47h");
170    }
171    return true;
172  }
173
174  public boolean releaseAlternateBuffer() throws IOException {
175    if (useAlternate) {
176      useAlternate = false;
177      writer.write("\033[?47l"); // Switches back to the normal screen
178    }
179    return true;
180  }
181
182  public CodeType decode(int code) {
183    if (code == command.getContext().verase) {
184      return CodeType.BACKSPACE;
185    } else {
186      switch (code) {
187        case HANDLED:
188          return CodeType.CLOSE;
189        case 1:
190          return CodeType.BEGINNING_OF_LINE;
191        case 5:
192          return CodeType.END_OF_LINE;
193        case 3:
194          return CodeType.BREAK;
195        case 9:
196          return CodeType.TAB;
197        case UP:
198          return CodeType.UP;
199        case DOWN:
200          return CodeType.DOWN;
201        case LEFT:
202          return CodeType.LEFT;
203        case RIGHT:
204          return CodeType.RIGHT;
205        case BACKWARD_WORD:
206          return CodeType.BACKWARD_WORD;
207        case FORWARD_WORD:
208          return CodeType.FORWARD_WORD;
209        default:
210          return CodeType.CHAR;
211      }
212    }
213  }
214
215  public void close() {
216    if (closed.get()) {
217      log.log(Level.FINE, "Attempt to closed again");
218    } else {
219      log.log(Level.FINE, "Closing SSHIO");
220      command.session.close(false);
221    }
222  }
223
224  public void flush() throws IOException {
225    writer.flush();
226  }
227
228  public void write(CharSequence s) throws IOException {
229    writer.write(s.toString());
230  }
231
232  public void write(char c) throws IOException {
233    writer.write(c);
234  }
235
236  public void write(Style d) throws IOException {
237    d.writeAnsiTo(writer);
238  }
239
240  public void writeDel() throws IOException {
241    writer.write("\033[D \033[D");
242  }
243
244  public void writeCRLF() throws IOException {
245    writer.write("\r\n");
246  }
247
248  public boolean moveRight(char c) throws IOException {
249    writer.write(c);
250    return true;
251  }
252
253  public boolean moveLeft() throws IOException {
254    writer.write("\033[");
255    writer.write("1D");
256    return true;
257  }
258
259  public void cls() throws IOException {
260    writer.write("\033[");
261    writer.write("2J");
262    writer.write("\033[");
263    writer.write("1;1H");
264  }
265}