| /******************************************************************************* |
| * 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; |
| } |
| |
| } |