001    /*
002     * Copyright (C) 2010 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.slf4j.Logger;
025    import org.slf4j.LoggerFactory;
026    
027    import java.io.*;
028    import java.util.concurrent.atomic.AtomicBoolean;
029    
030    /**
031     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
032     * @version $Revision$
033     */
034    public class SSHIO implements TermIO {
035    
036      /** Copied from net.wimpi.telnetd.io.TerminalIO. */
037      private static final int UP = 1001;
038    
039      /** Copied from net.wimpi.telnetd.io.TerminalIO. */
040      private static final int DOWN = 1002;
041    
042      /** Copied from net.wimpi.telnetd.io.TerminalIO. */
043      private static final int HANDLED = 1305;
044    
045      /** . */
046      private static final Logger log = LoggerFactory.getLogger(SSHIO.class);
047    
048      /** . */
049      private static final char DELETE_PREV_CHAR = 8;
050    
051      /** . */
052      private static final String DEL_SEQ = DELETE_PREV_CHAR + " " + DELETE_PREV_CHAR;
053    
054      /** . */
055      private final Reader reader;
056    
057      /** . */
058      private final Writer writer;
059    
060      /** . */
061      private static final int STATUS_NORMAL = 0;
062    
063      /** . */
064      private static final int STATUS_READ_ESC_1 = 1;
065    
066      /** . */
067      private static final int STATUS_READ_ESC_2 = 2;
068    
069      /** . */
070      private int status;
071    
072      /** . */
073      private final CRaSHCommand command;
074    
075      /** . */
076      final AtomicBoolean closed;
077    
078      public SSHIO(CRaSHCommand command) {
079        this.command = command;
080        this.writer = new OutputStreamWriter(command.out);
081        this.reader = new InputStreamReader(command.in);
082        this.status = STATUS_NORMAL;
083        this.closed = new AtomicBoolean(false);
084      }
085    
086      public int read() throws IOException {
087        while (true) {
088          if (closed.get()) {
089            return HANDLED;
090          } else {
091            int r;
092            try {
093              r = reader.read();
094            } catch (IOException e) {
095              // This would likely happen when the client close the connection
096              // when we are blocked on a read operation by the
097              // CRaShCommand#destroy() method
098              close();
099              return HANDLED;
100            }
101            if (r == -1) {
102              return HANDLED;
103            } else {
104              switch (status) {
105                case STATUS_NORMAL:
106                  if (r == 27) {
107                    status = STATUS_READ_ESC_1;
108                  } else {
109                    return r;
110                  }
111                  break;
112                case STATUS_READ_ESC_1:
113                  if (r == 91) {
114                    status = STATUS_READ_ESC_2;
115                  } else {
116                    status = STATUS_NORMAL;
117                    log.error("Unrecognized stream data " + r + " after reading ESC code");
118                  }
119                  break;
120                case STATUS_READ_ESC_2:
121                  status = STATUS_NORMAL;
122                  switch (r) {
123                    case 65:
124                      return UP;
125                    case 66:
126                      return DOWN;
127                    case 67:
128                      // Swallow RIGHT
129                      break;
130                    case 68:
131                      // Swallow LEFT
132                      break;
133                    default:
134                      log.error("Unrecognized stream data " + r + " after reading ESC+91 code");
135                      break;
136                  }
137              }
138            }
139          }
140        }
141      }
142    
143      public int getWidth() {
144        return command.getContext().getWidth();
145      }
146    
147      public String getProperty(String name) {
148        return command.getContext().getProperty(name);
149      }
150    
151      public CodeType decode(int code) {
152        if (code == command.getContext().verase) {
153          return CodeType.BACKSPACE;
154        } else {
155          switch (code) {
156            case HANDLED:
157              return CodeType.CLOSE;
158            case 3:
159              return CodeType.BREAK;
160            case 9:
161              return CodeType.TAB;
162            case UP:
163              return CodeType.UP;
164            case DOWN:
165              return CodeType.DOWN;
166            default:
167              return CodeType.CHAR;
168          }
169        }
170      }
171    
172      public void close() {
173        if (closed.get()) {
174          log.debug("Attempt to closed again");
175        } else {
176          log.debug("Closing SSHIO");
177          command.session.close(false);
178        }
179      }
180    
181      public void flush() throws IOException {
182        writer.flush();
183      }
184    
185      public void write(String s) throws IOException {
186        writer.write(s);
187      }
188    
189      public void write(char c) throws IOException {
190        writer.write(c);
191      }
192    
193      public void writeDel() throws IOException {
194        writer.write(DEL_SEQ);
195      }
196    
197      public void writeCRLF() throws IOException {
198        writer.write("\r\n");
199      }
200    
201      public boolean moveRight(char c) throws IOException {
202        return false;
203      }
204    
205      public boolean moveLeft() throws IOException {
206        return false;
207      }
208    }