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    
026    import java.io.*;
027    import java.util.concurrent.atomic.AtomicBoolean;
028    import java.util.logging.Level;
029    import java.util.logging.Logger;
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 int BACKWARD_WORD = -1;
050    
051      /** . */
052      private static final int FORWARD_WORD = -2;
053    
054      /** . */
055      private static final Logger log = Logger.getLogger(SSHIO.class.getName());
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 if (r == 98) {
119                    status = STATUS_NORMAL;
120                    return BACKWARD_WORD;
121                  } else if (r == 102) {
122                    status = STATUS_NORMAL;
123                    return FORWARD_WORD;
124                  } else {
125                    status = STATUS_NORMAL;
126                    log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC code");
127                  }
128                  break;
129                case STATUS_READ_ESC_2:
130                  status = STATUS_NORMAL;
131                  switch (r) {
132                    case 65:
133                      return UP;
134                    case 66:
135                      return DOWN;
136                    case 67:
137                      return RIGHT;
138                    case 68:
139                      return LEFT;
140                    default:
141                      log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC+91 code");
142                      break;
143                  }
144              }
145            }
146          }
147        }
148      }
149    
150      public int getWidth() {
151        return command.getContext().getWidth();
152      }
153    
154      public int getHeight() {
155        return command.getContext().getHeight();
156      }
157    
158      public String getProperty(String name) {
159        return command.getContext().getProperty(name);
160      }
161    
162      public CodeType decode(int code) {
163        if (code == command.getContext().verase) {
164          return CodeType.BACKSPACE;
165        } else {
166          switch (code) {
167            case HANDLED:
168              return CodeType.CLOSE;
169            case 1:
170              return CodeType.BEGINNING_OF_LINE;
171            case 5:
172              return CodeType.END_OF_LINE;
173            case 3:
174              return CodeType.BREAK;
175            case 9:
176              return CodeType.TAB;
177            case UP:
178              return CodeType.UP;
179            case DOWN:
180              return CodeType.DOWN;
181            case LEFT:
182              return CodeType.LEFT;
183            case RIGHT:
184              return CodeType.RIGHT;
185            case BACKWARD_WORD:
186              return CodeType.BACKWARD_WORD;
187            case FORWARD_WORD:
188              return CodeType.FORWARD_WORD;
189            default:
190              return CodeType.CHAR;
191          }
192        }
193      }
194    
195      public void close() {
196        if (closed.get()) {
197          log.log(Level.FINE, "Attempt to closed again");
198        } else {
199          log.log(Level.FINE, "Closing SSHIO");
200          command.session.close(false);
201        }
202      }
203    
204      public void flush() throws IOException {
205        writer.flush();
206      }
207    
208      public void write(CharSequence s) throws IOException {
209        writer.write(s.toString());
210      }
211    
212      public void write(char c) throws IOException {
213        writer.write(c);
214      }
215    
216      public void write(Style d) throws IOException {
217        d.writeAnsiTo(writer);
218      }
219    
220      public void writeDel() throws IOException {
221        writer.write("\033[D \033[D");
222      }
223    
224      public void writeCRLF() throws IOException {
225        writer.write("\r\n");
226      }
227    
228      public boolean moveRight(char c) throws IOException {
229        writer.write(c);
230        return true;
231      }
232    
233      public boolean moveLeft() throws IOException {
234        writer.write("\033[");
235        writer.write("1D");
236        return true;
237      }
238    
239      public void cls() throws IOException {
240        writer.write("\033[");
241        writer.write("2J");
242        writer.write("\033[");
243        writer.write("1;1H");
244      }
245    }