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