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    
020    package org.crsh.ssh.term;
021    
022    import org.crsh.term.CodeType;
023    import org.crsh.term.spi.TermIO;
024    import org.crsh.text.Style;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    
028    import java.io.*;
029    import java.util.concurrent.atomic.AtomicBoolean;
030    
031    public 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 Logger log = LoggerFactory.getLogger(SSHIO.class);
050    
051      /** . */
052      private final Reader reader;
053    
054      /** . */
055      private final Writer writer;
056    
057      /** . */
058      private static final int STATUS_NORMAL = 0;
059    
060      /** . */
061      private static final int STATUS_READ_ESC_1 = 1;
062    
063      /** . */
064      private static final int STATUS_READ_ESC_2 = 2;
065    
066      /** . */
067      private int status;
068    
069      /** . */
070      private final CRaSHCommand command;
071    
072      /** . */
073      final AtomicBoolean closed;
074    
075      public SSHIO(CRaSHCommand command) {
076        this.command = command;
077        this.writer = new OutputStreamWriter(command.out);
078        this.reader = new InputStreamReader(command.in);
079        this.status = STATUS_NORMAL;
080        this.closed = new AtomicBoolean(false);
081      }
082    
083      public int read() throws IOException {
084        while (true) {
085          if (closed.get()) {
086            return HANDLED;
087          } else {
088            int r;
089            try {
090              r = reader.read();
091            } catch (IOException e) {
092              // This would likely happen when the client close the connection
093              // when we are blocked on a read operation by the
094              // CRaShCommand#destroy() method
095              close();
096              return HANDLED;
097            }
098            if (r == -1) {
099              return HANDLED;
100            } else {
101              switch (status) {
102                case STATUS_NORMAL:
103                  if (r == 27) {
104                    status = STATUS_READ_ESC_1;
105                  } else {
106                    return r;
107                  }
108                  break;
109                case STATUS_READ_ESC_1:
110                  if (r == 91) {
111                    status = STATUS_READ_ESC_2;
112                  } else {
113                    status = STATUS_NORMAL;
114                    log.error("Unrecognized stream data " + r + " after reading ESC code");
115                  }
116                  break;
117                case STATUS_READ_ESC_2:
118                  status = STATUS_NORMAL;
119                  switch (r) {
120                    case 65:
121                      return UP;
122                    case 66:
123                      return DOWN;
124                    case 67:
125                      return RIGHT;
126                    case 68:
127                      return LEFT;
128                    default:
129                      log.error("Unrecognized stream data " + r + " after reading ESC+91 code");
130                      break;
131                  }
132              }
133            }
134          }
135        }
136      }
137    
138      public int getWidth() {
139        return command.getContext().getWidth();
140      }
141    
142      public int getHeight() {
143        return command.getContext().getHeight();
144      }
145    
146      public String getProperty(String name) {
147        return command.getContext().getProperty(name);
148      }
149    
150      public CodeType decode(int code) {
151        if (code == command.getContext().verase) {
152          return CodeType.BACKSPACE;
153        } else {
154          switch (code) {
155            case HANDLED:
156              return CodeType.CLOSE;
157            case 3:
158              return CodeType.BREAK;
159            case 9:
160              return CodeType.TAB;
161            case UP:
162              return CodeType.UP;
163            case DOWN:
164              return CodeType.DOWN;
165            case LEFT:
166              return CodeType.LEFT;
167            case RIGHT:
168              return CodeType.RIGHT;
169            default:
170              return CodeType.CHAR;
171          }
172        }
173      }
174    
175      public void close() {
176        if (closed.get()) {
177          log.debug("Attempt to closed again");
178        } else {
179          log.debug("Closing SSHIO");
180          command.session.close(false);
181        }
182      }
183    
184      public void flush() throws IOException {
185        writer.flush();
186      }
187    
188      public void write(CharSequence s) throws IOException {
189        writer.write(s.toString());
190      }
191    
192      public void write(char c) throws IOException {
193        writer.write(c);
194      }
195    
196      public void write(Style d) throws IOException {
197        d.writeAnsiTo(writer);
198      }
199    
200      public void writeDel() throws IOException {
201        writer.write("\033[D \033[D");
202      }
203    
204      public void writeCRLF() throws IOException {
205        writer.write("\r\n");
206      }
207    
208      public boolean moveRight(char c) throws IOException {
209        writer.write(c);
210        return true;
211      }
212    
213      public boolean moveLeft() throws IOException {
214        writer.write("\033[");
215        writer.write("1D");
216        return true;
217      }
218    
219      public void cls() throws IOException {
220        writer.write("\033[");
221        writer.write("2J");
222        writer.write("\033[");
223        writer.write("1;1H");
224      }
225    }