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 }