/*
* This file is part of lanterna (http://code.google.com/p/lanterna/).
*
* lanterna is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
* A good resource on telnet communication is http://www.tcpipguide.com/free/t_TelnetProtocol.htm
* Also here: http://support.microsoft.com/kb/231866
* @see TelnetTerminalServer
* @author martin
*/
public class TelnetTerminal extends ANSITerminal {
private final Socket socket;
private final NegotiationState negotiationState;
TelnetTerminal(Socket socket, Charset terminalCharset) throws IOException {
this(socket, new TelnetClientIACFilterer(socket.getInputStream()), socket.getOutputStream(), terminalCharset);
}
//This weird construction is just so that we can access the input filter without changing the visibility in StreamBasedTerminal
private TelnetTerminal(Socket socket, TelnetClientIACFilterer inputStream, OutputStream outputStream, Charset terminalCharset) throws IOException {
super(inputStream, outputStream, terminalCharset);
this.socket = socket;
this.negotiationState = inputStream.negotiationState;
inputStream.setEventListener(new TelnetClientEventListener() {
@Override
public void onResize(int columns, int rows) {
TelnetTerminal.this.onResized(columns, rows);
}
@Override
public void requestReply(boolean will, byte option) throws IOException {
writeToTerminal(COMMAND_IAC, will ? COMMAND_WILL : COMMAND_WONT, option);
}
});
setLineMode0();
setEchoOff();
setResizeNotificationOn();
}
/**
* Returns the socket address for the remote endpoint of the telnet connection
* @return SocketAddress representing the remote client
*/
public SocketAddress getRemoteSocketAddress() {
return socket.getRemoteSocketAddress();
}
private void setEchoOff() throws IOException {
writeToTerminal(COMMAND_IAC, COMMAND_WILL, OPTION_ECHO);
flush();
}
private void setLineMode0() throws IOException {
writeToTerminal(
COMMAND_IAC, COMMAND_DO, OPTION_LINEMODE,
COMMAND_IAC, COMMAND_SUBNEGOTIATION, OPTION_LINEMODE, (byte)1, (byte)0, COMMAND_IAC, COMMAND_SUBNEGOTIATION_END);
flush();
}
private void setResizeNotificationOn() throws IOException {
writeToTerminal(
COMMAND_IAC, COMMAND_DO, OPTION_NAWS);
flush();
}
/**
* Retrieves the current negotiation state with the client, containing details on what options have been enabled
* and what the client has said it supports.
* @return The current negotiation state for this client
*/
public NegotiationState getNegotiationState() {
return negotiationState;
}
/**
* Closes the socket to the client, effectively ending the telnet session and the terminal.
* @throws IOException If there was an underlying I/O error
*/
public void close() throws IOException {
socket.close();
}
/**
* This class contains some of the various states that the Telnet negotiation protocol defines. Lanterna doesn't
* support all of them but the more common ones are represented.
*/
public static class NegotiationState {
private boolean clientEcho;
private boolean clientLineMode0;
private boolean clientResizeNotification;
private boolean suppressGoAhead;
private boolean extendedAscii;
NegotiationState() {
this.clientEcho = true;
this.clientLineMode0 = false;
this.clientResizeNotification = false;
this.suppressGoAhead = true;
this.extendedAscii = true;
}
/**
* Is the telnet client echo mode turned on (client is echoing characters locally)
* @return {@code true} if client echo is enabled
*/
public boolean isClientEcho() {
return clientEcho;
}
/**
* Is the telnet client line mode 0 turned on (client sends character by character instead of line by line)
* @return {@code true} if client line mode 0 is enabled
*/
public boolean isClientLineMode0() {
return clientLineMode0;
}
/**
* Is the telnet client resize notification turned on (client notifies server when the terminal window has
* changed size)
* @return {@code true} if client resize notification is enabled
*/
public boolean isClientResizeNotification() {
return clientResizeNotification;
}
/**
* Is the telnet client suppress go-ahead turned on
* @return {@code true} if client suppress go-ahead is enabled
*/
public boolean isSuppressGoAhead() {
return suppressGoAhead;
}
/**
* Is the telnet client extended ascii turned on
* @return {@code true} if client extended ascii is enabled
*/
public boolean isExtendedAscii() {
return extendedAscii;
}
private void onUnsupportedStateCommand(boolean enabling, byte value) {
System.err.println("Unsupported operation: Client says it " + (enabling ? "will" : "won't") + " do " + TelnetProtocol.CODE_TO_NAME.get(value));
}
private void onUnsupportedRequestCommand(boolean askedToDo, byte value) {
System.err.println("Unsupported request: Client asks us, " + (askedToDo ? "do" : "don't") + " " + TelnetProtocol.CODE_TO_NAME.get(value));
}
private void onUnsupportedSubnegotiation(byte option, byte[] additionalData) {
System.err.println("Unsupported subnegotiation: Client send " + TelnetProtocol.CODE_TO_NAME.get(option) + " with extra data " +
toList(additionalData));
}
private static List