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