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