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