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