Version 2.0.0: update sources
[jvcard.git] / src / com / googlecode / lanterna / terminal / ansi / TelnetTerminal.java
diff --git a/src/com/googlecode/lanterna/terminal/ansi/TelnetTerminal.java b/src/com/googlecode/lanterna/terminal/ansi/TelnetTerminal.java
deleted file mode 100644 (file)
index 8de5df0..0000000
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- *
- * Copyright (C) 2010-2015 Martin
- */
-package com.googlecode.lanterna.terminal.ansi;
-
-import static com.googlecode.lanterna.terminal.ansi.TelnetProtocol.*;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This class is used by the {@code TelnetTerminalServer} class when a client has connected in; this class will be the
- * interaction point for that client. All operations are sent to the client over the network socket and some of the
- * meta-operations (like echo mode) are communicated using Telnet negotiation language. You can't create objects of this
- * class directly; they are created for you when you are listening for incoming connections using a
- * {@code TelnetTerminalServer} and a client connects.
- * <p>
- * A good resource on telnet communication is http://www.tcpipguide.com/free/t_TelnetProtocol.htm<br>
- * 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<String> toList(byte[] array) {
-            List<String> list = new ArrayList<String>(array.length);
-            for(byte b: array) {
-                list.add(String.format("%02X ", b));
-            }
-            return list;
-        }
-    }
-    
-    private interface TelnetClientEventListener {
-        void onResize(int columns, int rows);
-        void requestReply(boolean will, byte option) throws IOException;
-    }
-    
-    private static class TelnetClientIACFilterer extends InputStream {
-        private final NegotiationState negotiationState;
-        private final InputStream inputStream;
-        private final byte[] buffer;
-        private final byte[] workingBuffer;
-        private int bytesInBuffer;
-        private TelnetClientEventListener eventListener;
-
-        TelnetClientIACFilterer(InputStream inputStream) {
-            this.negotiationState = new NegotiationState();
-            this.inputStream = inputStream;
-            this.buffer = new byte[64 * 1024];
-            this.workingBuffer = new byte[1024];
-            this.bytesInBuffer = 0;
-            this.eventListener = null;
-        }
-
-        private void setEventListener(TelnetClientEventListener eventListener) {
-            this.eventListener = eventListener;
-        }
-
-        @Override
-        public int read() throws IOException {
-            throw new UnsupportedOperationException("TelnetClientIACFilterer doesn't support .read()");
-        }
-
-        @Override
-        public void close() throws IOException {
-            inputStream.close();
-        }
-
-        @Override
-        public int available() throws IOException {
-            int underlyingStreamAvailable = inputStream.available();
-            if(underlyingStreamAvailable == 0 && bytesInBuffer == 0) {
-                return 0;
-            }
-            else if(underlyingStreamAvailable == 0) {
-                return bytesInBuffer;
-            }
-            else if(bytesInBuffer == buffer.length) {
-                return bytesInBuffer;
-            }
-            fillBuffer();
-            return bytesInBuffer;
-        }
-
-        @Override
-        @SuppressWarnings("NullableProblems")   //I can't find the correct way to fix this!
-        public int read(byte[] b, int off, int len) throws IOException {
-            if(inputStream.available() > 0) {
-                fillBuffer();
-            }
-            if(bytesInBuffer == 0) {
-                return -1;
-            }
-            int bytesToCopy = Math.min(len, bytesInBuffer);
-            System.arraycopy(buffer, 0, b, off, bytesToCopy);
-            System.arraycopy(buffer, bytesToCopy, buffer, 0, buffer.length - bytesToCopy);
-            bytesInBuffer -= bytesToCopy;
-            return bytesToCopy;
-        }
-
-        private void fillBuffer() throws IOException {
-            int readBytes = inputStream.read(workingBuffer, 0, Math.min(workingBuffer.length, buffer.length - bytesInBuffer));
-            if(readBytes == -1) {
-                return;
-            }
-            for(int i = 0; i < readBytes; i++) {
-                if(workingBuffer[i] == COMMAND_IAC) {
-                    i++;
-                    if(Arrays.asList(COMMAND_DO, COMMAND_DONT, COMMAND_WILL, COMMAND_WONT).contains(workingBuffer[i])) {
-                        parseCommand(workingBuffer, i, readBytes);
-                        ++i;
-                        continue;
-                    }
-                    else if(workingBuffer[i] == COMMAND_SUBNEGOTIATION) {   //0xFA = SB = Subnegotiation
-                        i += parseSubNegotiation(workingBuffer, ++i, readBytes);
-                        continue;
-                    }
-                    else if(workingBuffer[i] != COMMAND_IAC) {   //Double IAC = 255
-                        System.err.println("Unknown Telnet command: " + workingBuffer[i]);
-                    }
-                }
-                buffer[bytesInBuffer++] = workingBuffer[i];
-            }
-        }
-        
-        private void parseCommand(byte[] buffer, int position, int max) throws IOException {
-            if(position + 1 >= max) {
-                throw new IllegalStateException("State error, we got a command signal from the remote telnet client but "
-                        + "not enough characters available in the stream");
-            }
-            byte command = buffer[position];
-            byte value = buffer[position + 1];
-            switch(command) {
-                case COMMAND_DO:
-                case COMMAND_DONT:
-                    if(value == OPTION_SUPPRESS_GO_AHEAD) {
-                        negotiationState.suppressGoAhead = (command == COMMAND_DO);
-                        eventListener.requestReply(command == COMMAND_DO, value);
-                    }
-                    else if(value == OPTION_EXTEND_ASCII) {
-                        negotiationState.extendedAscii = (command == COMMAND_DO);
-                        eventListener.requestReply(command == COMMAND_DO, value);
-                    }
-                    else {
-                        negotiationState.onUnsupportedRequestCommand(command == COMMAND_DO, value);
-                    }
-                    break;
-                case COMMAND_WILL:
-                case COMMAND_WONT:
-                    if(value == OPTION_ECHO) {
-                        negotiationState.clientEcho = (command == COMMAND_WILL);
-                    }
-                    else if(value == OPTION_LINEMODE) {
-                        negotiationState.clientLineMode0 = (command == COMMAND_WILL);
-                    }
-                    else if(value == OPTION_NAWS) {
-                       negotiationState.clientResizeNotification = (command == COMMAND_WILL);
-                    }
-                    else {
-                        negotiationState.onUnsupportedStateCommand(command == COMMAND_WILL, value);
-                    }
-                    break;
-                default:
-                    throw new UnsupportedOperationException("No command handler implemented for " + TelnetProtocol.CODE_TO_NAME.get(command));
-            }
-        }
-        
-        private int parseSubNegotiation(byte[] buffer, int position, int max) {
-            int originalPosition = position;
-
-            //Read operation
-            byte operation = buffer[position++];
-            
-            //Read until [IAC SE]            
-            ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
-            while(position < max) {
-                byte read = buffer[position];
-                if(read != COMMAND_IAC) {
-                    outputBuffer.write(read);
-                }
-                else {
-                    if(position + 1 == max) {
-                        throw new IllegalStateException("State error, unexpected end of buffer when reading subnegotiation");
-                    }
-                    position++;
-                    if(buffer[position] == COMMAND_IAC) {
-                        outputBuffer.write(COMMAND_IAC);    //Escaped IAC
-                    }
-                    else if(buffer[position] == COMMAND_SUBNEGOTIATION_END) {
-                        parseSubNegotiation(operation, outputBuffer.toByteArray());
-                        return ++position - originalPosition;
-                    }
-                }
-                position++;
-            }
-            throw new IllegalStateException("State error, unexpected end of buffer when reading subnegotiation, no IAC SE");
-        }
-
-        private void parseSubNegotiation(byte option, byte[] additionalData) {
-            switch(option) {
-                case OPTION_NAWS:
-                    eventListener.onResize(
-                            convertTwoBytesToInt2(additionalData[1], additionalData[0]), 
-                            convertTwoBytesToInt2(additionalData[3], additionalData[2]));
-                    break;
-                case OPTION_LINEMODE:
-                    //We don't parse this, as this is a very complicated command :(
-                    //Let's leave it for now, fingers crossed
-                    break;
-                default:
-                    negotiationState.onUnsupportedSubnegotiation(option, additionalData);
-                    break;
-            }
-        }
-    }
-    
-    private static int convertTwoBytesToInt2(byte b1, byte b2) {
-        return ( (b2 & 0xFF) << 8) | (b1 & 0xFF);
-    }
-}