blob: b285d815978eb091d9eab4aa7293c8245c90207e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2017 SAP AG and others
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Lazar Kirchev, SAP AG - initial API and implementation
* IBM Corporation - ongoing development
*******************************************************************************/
package org.eclipse.equinox.console.telnet;
import org.eclipse.equinox.console.common.ConsoleInputStream;
import org.eclipse.equinox.console.common.ConsoleOutputStream;
import org.eclipse.equinox.console.common.KEYS;
import org.eclipse.equinox.console.common.Scanner;
import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* This class performs the processing of the telnet commands,
* and updates respectively what is displayed in the output. Also, it performs
* terminal type negotiation with the telnet client. This is important for some of the escape sequences,
* which are different for the different terminal types. Without such negotiation,
* some keys (such as backspace, del, insert, home, etc.) may not be correctly
* interpreted by the telnet server. Currently the supported terminal types are
* ANSI, VT100, VT220, VT320, XTERM and SCO. The support is limited to the following
* keys - BACKSPACE, DEL, INSERT, HOME, END, PAGEUP, PAGEDOWN, ARROWS.
*/
public class TelnetInputScanner extends Scanner {
private boolean isCommand = false;
private boolean isReadingTtype = false;
private boolean shouldFinish = false;
private boolean tTypeNegotiationStarted = false;
private int lastRead = -1;
private ArrayList<Integer> currentTerminalType = new ArrayList<>();
private ArrayList<Integer> lastTerminalType = null;
private Set<String> supportedTerminalTypes = new HashSet<>();
private Callback callback;
public TelnetInputScanner(ConsoleInputStream toShell, ConsoleOutputStream toTelnet, Callback callback) {
super(toShell, toTelnet);
initializeSupportedTerminalTypes();
TerminalTypeMappings currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE);
currentEscapesToKey = currentMapping.getEscapesToKey();
escapes = currentMapping.getEscapes();
setBackspace(currentMapping.getBackspace());
setDel(currentMapping.getDel());
this.callback = callback;
}
private void initializeSupportedTerminalTypes() {
supportedTerminalTypes.add("ANSI");
supportedTerminalTypes.add("VT100");
supportedTerminalTypes.add("VT220");
supportedTerminalTypes.add("VT320");
supportedTerminalTypes.add("XTERM");
supportedTerminalTypes.add("SCO");
}
@Override
public void scan(int b) throws IOException {
b &= 0xFF;
if (isEsc) {
scanEsc(b);
} else if (isCommand) {
scanCommand(b);
} else if (b == IAC) {
startCommand();
} else {
switch (b) {
case ESC:
startEsc();
toShell.add(new byte[]{(byte) b});
break;
default:
if (b >= SPACE && b < MAX_CHAR) {
echo((byte) b);
flush();
}
toShell.add(new byte[]{(byte) b});
}
}
lastRead = b;
}
/* Telnet command codes are described in RFC 854, TELNET PROTOCOL SPECIFICATION
* available at http://www.ietf.org/rfc/rfc854.txt
*
* Telnet terminal type negotiation option is described in RFC 1091, Telnet Terminal-Type Option
* available at http://www.ietf.org/rfc/rfc1091.txt
*/
private static final int SE = 240;
private static final int EC = 247;
private static final int EL = 248;
private static final int SB = 250;
private static final int WILL = 251;
private static final int WILL_NOT = 252;
private static final int DO = 253;
private static final int DO_NOT = 254;
private static final int TTYPE = 24;
private static final int SEND = 1;
private static final int IAC = 255;
private static final int IS = 0;
private boolean isNegotiation;
private boolean isWill;
private byte[] tTypeRequest = {(byte)IAC, (byte)SB, (byte)TTYPE, (byte)SEND, (byte)IAC, (byte)SE};
private void scanCommand(final int b) throws IOException {
if (isNegotiation) {
scanNegotiation(b);
} else if (isWill) {
isWill = false;
isCommand = false;
if(b == TTYPE && tTypeNegotiationStarted == false) {
sendRequest();
}
} else {
switch (b) {
case WILL:
isWill = true;
break;
case WILL_NOT:
break;
case DO:
break;
case DO_NOT:
break;
case SB:
isNegotiation = true;
break;
case EC:
eraseChar();
isCommand = false;
break;
case EL:
default:
isCommand = false;
break;
}
}
}
private void scanNegotiation(final int b) {
if (lastRead == SB && b == TTYPE) {
isReadingTtype = true;
} else if (b == IS) {
} else if (b == IAC) {
} else if (b == SE) {
isNegotiation = false;
isCommand = false;
if (isReadingTtype == true) {
isReadingTtype = false;
if (shouldFinish == true) {
setCurrentTerminalType();
shouldFinish = false;
return;
}
boolean isMatch = isTerminalTypeSupported();
boolean isLast = isLast();
if (isMatch == true) {
setCurrentTerminalType();
return;
}
lastTerminalType = currentTerminalType;
currentTerminalType = new ArrayList<>();
if (isLast == true && isMatch == false) {
shouldFinish = true;
sendRequest();
} else if (isLast == false && isMatch == false) {
sendRequest();
}
}
} else if (isReadingTtype == true){
currentTerminalType.add(b);
}
}
private boolean isTerminalTypeSupported() {
byte[] tmp = new byte[currentTerminalType.size()];
int idx = 0;
for(Integer i : currentTerminalType) {
tmp[idx] = i.byteValue();
idx++;
}
String tType = new String(tmp);
for(String terminal : supportedTerminalTypes) {
if(tType.toUpperCase().contains(terminal)) {
return true;
}
}
return false;
}
private boolean isLast() {
if(currentTerminalType.equals(lastTerminalType)) {
return true;
} else {
return false;
}
}
private void setCurrentTerminalType() {
byte[] tmp = new byte[currentTerminalType.size()];
int idx = 0;
for(Integer i : currentTerminalType) {
tmp[idx] = i.byteValue();
idx++;
}
String tType = new String(tmp);
String term = null;
for(String terminal : supportedTerminalTypes) {
if(tType.toUpperCase().contains(terminal)) {
term = terminal;
}
}
TerminalTypeMappings currentMapping = supportedEscapeSequences.get(term);
if(currentMapping == null) {
currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE);
}
currentEscapesToKey = currentMapping.getEscapesToKey();
escapes = currentMapping.getEscapes();
setBackspace(currentMapping.getBackspace());
setDel(currentMapping.getDel());
if(callback != null) {
callback.finished();
}
}
private void sendRequest() {
try {
toTelnet.write(tTypeRequest);
toTelnet.flush();
if(tTypeNegotiationStarted == false) {
tTypeNegotiationStarted = true;
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void startCommand() {
isCommand = true;
isNegotiation = false;
isWill = false;
}
private void eraseChar() throws IOException {
toShell.add(new byte[]{BS});
}
@Override
protected void scanEsc(int b) throws IOException {
esc += (char) b;
toShell.add(new byte[]{(byte) b});
KEYS key = checkEscape(esc);
if (key == KEYS.UNFINISHED) {
return;
}
if (key == KEYS.UNKNOWN) {
isEsc = false;
scan(b);
return;
}
isEsc = false;
}
}