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 }