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 026 import java.io.*; 027 import java.util.concurrent.atomic.AtomicBoolean; 028 import java.util.logging.Level; 029 import java.util.logging.Logger; 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 int BACKWARD_WORD = -1; 050 051 /** . */ 052 private static final int FORWARD_WORD = -2; 053 054 /** . */ 055 private static final Logger log = Logger.getLogger(SSHIO.class.getName()); 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 /** . */ 082 private boolean useAlternate; 083 084 public SSHIO(CRaSHCommand command) { 085 this.command = command; 086 this.writer = new OutputStreamWriter(command.out); 087 this.reader = new InputStreamReader(command.in); 088 this.status = STATUS_NORMAL; 089 this.closed = new AtomicBoolean(false); 090 this.useAlternate = false; 091 } 092 093 public int read() throws IOException { 094 while (true) { 095 if (closed.get()) { 096 return HANDLED; 097 } else { 098 int r; 099 try { 100 r = reader.read(); 101 } catch (IOException e) { 102 // This would likely happen when the client close the connection 103 // when we are blocked on a read operation by the 104 // CRaShCommand#destroy() method 105 close(); 106 return HANDLED; 107 } 108 if (r == -1) { 109 return HANDLED; 110 } else { 111 switch (status) { 112 case STATUS_NORMAL: 113 if (r == 27) { 114 status = STATUS_READ_ESC_1; 115 } else { 116 return r; 117 } 118 break; 119 case STATUS_READ_ESC_1: 120 if (r == 91) { 121 status = STATUS_READ_ESC_2; 122 } else if (r == 98) { 123 status = STATUS_NORMAL; 124 return BACKWARD_WORD; 125 } else if (r == 102) { 126 status = STATUS_NORMAL; 127 return FORWARD_WORD; 128 } else { 129 status = STATUS_NORMAL; 130 log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC code"); 131 } 132 break; 133 case STATUS_READ_ESC_2: 134 status = STATUS_NORMAL; 135 switch (r) { 136 case 65: 137 return UP; 138 case 66: 139 return DOWN; 140 case 67: 141 return RIGHT; 142 case 68: 143 return LEFT; 144 default: 145 log.log(Level.SEVERE, "Unrecognized stream data " + r + " after reading ESC+91 code"); 146 break; 147 } 148 } 149 } 150 } 151 } 152 } 153 154 public int getWidth() { 155 return command.getContext().getWidth(); 156 } 157 158 public int getHeight() { 159 return command.getContext().getHeight(); 160 } 161 162 public String getProperty(String name) { 163 return command.getContext().getProperty(name); 164 } 165 166 public boolean takeAlternateBuffer() throws IOException { 167 if (!useAlternate) { 168 useAlternate = true; 169 writer.write("\033[?47h"); 170 } 171 return true; 172 } 173 174 public boolean releaseAlternateBuffer() throws IOException { 175 if (useAlternate) { 176 useAlternate = false; 177 writer.write("\033[?47l"); // Switches back to the normal screen 178 } 179 return true; 180 } 181 182 public CodeType decode(int code) { 183 if (code == command.getContext().verase) { 184 return CodeType.BACKSPACE; 185 } else { 186 switch (code) { 187 case HANDLED: 188 return CodeType.CLOSE; 189 case 1: 190 return CodeType.BEGINNING_OF_LINE; 191 case 5: 192 return CodeType.END_OF_LINE; 193 case 3: 194 return CodeType.BREAK; 195 case 9: 196 return CodeType.TAB; 197 case UP: 198 return CodeType.UP; 199 case DOWN: 200 return CodeType.DOWN; 201 case LEFT: 202 return CodeType.LEFT; 203 case RIGHT: 204 return CodeType.RIGHT; 205 case BACKWARD_WORD: 206 return CodeType.BACKWARD_WORD; 207 case FORWARD_WORD: 208 return CodeType.FORWARD_WORD; 209 default: 210 return CodeType.CHAR; 211 } 212 } 213 } 214 215 public void close() { 216 if (closed.get()) { 217 log.log(Level.FINE, "Attempt to closed again"); 218 } else { 219 log.log(Level.FINE, "Closing SSHIO"); 220 command.session.close(false); 221 } 222 } 223 224 public void flush() throws IOException { 225 writer.flush(); 226 } 227 228 public void write(CharSequence s) throws IOException { 229 writer.write(s.toString()); 230 } 231 232 public void write(char c) throws IOException { 233 writer.write(c); 234 } 235 236 public void write(Style d) throws IOException { 237 d.writeAnsiTo(writer); 238 } 239 240 public void writeDel() throws IOException { 241 writer.write("\033[D \033[D"); 242 } 243 244 public void writeCRLF() throws IOException { 245 writer.write("\r\n"); 246 } 247 248 public boolean moveRight(char c) throws IOException { 249 writer.write(c); 250 return true; 251 } 252 253 public boolean moveLeft() throws IOException { 254 writer.write("\033["); 255 writer.write("1D"); 256 return true; 257 } 258 259 public void cls() throws IOException { 260 writer.write("\033["); 261 writer.write("2J"); 262 writer.write("\033["); 263 writer.write("1;1H"); 264 } 265 }