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