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.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    /**
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 char DELETE_PREV_CHAR = 8;
051    
052      /** . */
053      private static final String DEL_SEQ = DELETE_PREV_CHAR + " " + DELETE_PREV_CHAR;
054    
055      /** . */
056      private final Reader reader;
057    
058      /** . */
059      private final Writer writer;
060    
061      /** . */
062      private static final int STATUS_NORMAL = 0;
063    
064      /** . */
065      private static final int STATUS_READ_ESC_1 = 1;
066    
067      /** . */
068      private static final int STATUS_READ_ESC_2 = 2;
069    
070      /** . */
071      private int status;
072    
073      /** . */
074      private final CRaSHCommand command;
075    
076      /** . */
077      final AtomicBoolean closed;
078    
079      public SSHIO(CRaSHCommand command) {
080        this.command = command;
081        this.writer = new OutputStreamWriter(command.out);
082        this.reader = new InputStreamReader(command.in);
083        this.status = STATUS_NORMAL;
084        this.closed = new AtomicBoolean(false);
085      }
086    
087      public int read() throws IOException {
088        while (true) {
089          if (closed.get()) {
090            return HANDLED;
091          } else {
092            int r;
093            try {
094              r = reader.read();
095            } catch (IOException e) {
096              // This would likely happen when the client close the connection
097              // when we are blocked on a read operation by the
098              // CRaShCommand#destroy() method
099              close();
100              return HANDLED;
101            }
102            if (r == -1) {
103              return HANDLED;
104            } else {
105              switch (status) {
106                case STATUS_NORMAL:
107                  if (r == 27) {
108                    status = STATUS_READ_ESC_1;
109                  } else {
110                    return r;
111                  }
112                  break;
113                case STATUS_READ_ESC_1:
114                  if (r == 91) {
115                    status = STATUS_READ_ESC_2;
116                  } else {
117                    status = STATUS_NORMAL;
118                    log.error("Unrecognized stream data " + r + " after reading ESC code");
119                  }
120                  break;
121                case STATUS_READ_ESC_2:
122                  status = STATUS_NORMAL;
123                  switch (r) {
124                    case 65:
125                      return UP;
126                    case 66:
127                      return DOWN;
128                    case 67:
129                      // Swallow RIGHT
130                      break;
131                    case 68:
132                      // Swallow LEFT
133                      break;
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 String getProperty(String name) {
149        return command.getContext().getProperty(name);
150      }
151    
152      public CodeType decode(int code) {
153        if (code == command.getContext().verase) {
154          return CodeType.BACKSPACE;
155        } else {
156          switch (code) {
157            case HANDLED:
158              return CodeType.CLOSE;
159            case 3:
160              return CodeType.BREAK;
161            case 9:
162              return CodeType.TAB;
163            case UP:
164              return CodeType.UP;
165            case DOWN:
166              return CodeType.DOWN;
167            default:
168              return CodeType.CHAR;
169          }
170        }
171      }
172    
173      public void close() {
174        if (closed.get()) {
175          log.debug("Attempt to closed again");
176        } else {
177          log.debug("Closing SSHIO");
178          command.session.close(false);
179        }
180      }
181    
182      public void flush() throws IOException {
183        writer.flush();
184      }
185    
186      public void write(String s) throws IOException {
187        writer.write(s);
188      }
189    
190      public void write(char c) throws IOException {
191        writer.write(c);
192      }
193    
194      public void write(Style d) throws IOException {
195        d.writeAnsiTo(writer);
196      }
197    
198      public void writeDel() throws IOException {
199        writer.write(DEL_SEQ);
200      }
201    
202      public void writeCRLF() throws IOException {
203        writer.write("\r\n");
204      }
205    
206      public boolean moveRight(char c) throws IOException {
207        return false;
208      }
209    
210      public boolean moveLeft() throws IOException {
211        return false;
212      }
213    }