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 }