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