001 /*
002 * Copyright (C) 2010 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 /**
032 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
033 * @version $Revision$
034 */
035 public class SSHIO implements TermIO {
036
037 /** Copied from net.wimpi.telnetd.io.TerminalIO. */
038 private static final int UP = 1001;
039
040 /** Copied from net.wimpi.telnetd.io.TerminalIO. */
041 private static final int DOWN = 1002;
042
043 /** Copied from net.wimpi.telnetd.io.TerminalIO. */
044 private static final int HANDLED = 1305;
045
046 /** . */
047 private static final Logger log = LoggerFactory.getLogger(SSHIO.class);
048
049 /** . */
050 private static final char DELETE_PREV_CHAR = 8;
051
052 /** . */
053 private static final String DEL_SEQ = DELETE_PREV_CHAR + " " + DELETE_PREV_CHAR;
054
055 /** . */
056 private final Reader reader;
057
058 /** . */
059 private final Writer writer;
060
061 /** . */
062 private static final int STATUS_NORMAL = 0;
063
064 /** . */
065 private static final int STATUS_READ_ESC_1 = 1;
066
067 /** . */
068 private static final int STATUS_READ_ESC_2 = 2;
069
070 /** . */
071 private int status;
072
073 /** . */
074 private final CRaSHCommand command;
075
076 /** . */
077 final AtomicBoolean closed;
078
079 public SSHIO(CRaSHCommand command) {
080 this.command = command;
081 this.writer = new OutputStreamWriter(command.out);
082 this.reader = new InputStreamReader(command.in);
083 this.status = STATUS_NORMAL;
084 this.closed = new AtomicBoolean(false);
085 }
086
087 public int read() throws IOException {
088 while (true) {
089 if (closed.get()) {
090 return HANDLED;
091 } else {
092 int r;
093 try {
094 r = reader.read();
095 } catch (IOException e) {
096 // This would likely happen when the client close the connection
097 // when we are blocked on a read operation by the
098 // CRaShCommand#destroy() method
099 close();
100 return HANDLED;
101 }
102 if (r == -1) {
103 return HANDLED;
104 } else {
105 switch (status) {
106 case STATUS_NORMAL:
107 if (r == 27) {
108 status = STATUS_READ_ESC_1;
109 } else {
110 return r;
111 }
112 break;
113 case STATUS_READ_ESC_1:
114 if (r == 91) {
115 status = STATUS_READ_ESC_2;
116 } else {
117 status = STATUS_NORMAL;
118 log.error("Unrecognized stream data " + r + " after reading ESC code");
119 }
120 break;
121 case STATUS_READ_ESC_2:
122 status = STATUS_NORMAL;
123 switch (r) {
124 case 65:
125 return UP;
126 case 66:
127 return DOWN;
128 case 67:
129 // Swallow RIGHT
130 break;
131 case 68:
132 // Swallow LEFT
133 break;
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 String getProperty(String name) {
149 return command.getContext().getProperty(name);
150 }
151
152 public CodeType decode(int code) {
153 if (code == command.getContext().verase) {
154 return CodeType.BACKSPACE;
155 } else {
156 switch (code) {
157 case HANDLED:
158 return CodeType.CLOSE;
159 case 3:
160 return CodeType.BREAK;
161 case 9:
162 return CodeType.TAB;
163 case UP:
164 return CodeType.UP;
165 case DOWN:
166 return CodeType.DOWN;
167 default:
168 return CodeType.CHAR;
169 }
170 }
171 }
172
173 public void close() {
174 if (closed.get()) {
175 log.debug("Attempt to closed again");
176 } else {
177 log.debug("Closing SSHIO");
178 command.session.close(false);
179 }
180 }
181
182 public void flush() throws IOException {
183 writer.flush();
184 }
185
186 public void write(String s) throws IOException {
187 writer.write(s);
188 }
189
190 public void write(char c) throws IOException {
191 writer.write(c);
192 }
193
194 public void write(Style d) throws IOException {
195 d.writeAnsiTo(writer);
196 }
197
198 public void writeDel() throws IOException {
199 writer.write(DEL_SEQ);
200 }
201
202 public void writeCRLF() throws IOException {
203 writer.write("\r\n");
204 }
205
206 public boolean moveRight(char c) throws IOException {
207 return false;
208 }
209
210 public boolean moveLeft() throws IOException {
211 return false;
212 }
213 }