telnet daemon working
authorKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 25 Mar 2015 16:59:54 +0000 (12:59 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Wed, 25 Mar 2015 16:59:54 +0000 (12:59 -0400)
LICENSE
README.md
build.xml
src/jexer/io/ECMA48Terminal.java
src/jexer/net/TelnetInputStream.java
src/jexer/net/TelnetOutputStream.java
src/jexer/net/TelnetServerSocket.java
src/jexer/net/TelnetSocket.java
src/jexer/net/package-info.java [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
index 8e7005ecd4031b9651aaba383bf25930e2eb475f..dc98dc606e27ac2a0b52ad9fc26f19953dc160e8 100644 (file)
--- a/LICENSE
+++ b/LICENSE
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
+This project is licensed LGPL ("GNU Lesser General Public License",
+sometimes called the "Library GPL") version 3 or greater.  You may
+freely use this project in both closed source (proprietary) and open
+source applications, however any changes you make to the code must be
+made available to your users.
+
+The LGPLv3 license is a supplement to the GPLv3 license.  Both
+licenses are reproduced below.
+
+---- LGPLv3 License Text ----
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
+
+---- GPLv3 License Text ----
+
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 3, 29 June 2007
 
  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-                            Preamble
+                           Preamble
 
   The GNU General Public License is a free, copyleft license for
 software and other kinds of works.
@@ -68,7 +249,7 @@ patents cannot be used to render the program non-free.
   The precise terms and conditions for copying, distribution and
 modification follow.
 
-                       TERMS AND CONDITIONS
+                      TERMS AND CONDITIONS
 
   0. Definitions.
 
@@ -618,9 +799,9 @@ an absolute waiver of all civil liability in connection with the
 Program, unless a warranty or assumption of liability accompanies a
 copy of the Program in return for a fee.
 
-                     END OF TERMS AND CONDITIONS
+                    END OF TERMS AND CONDITIONS
 
-            How to Apply These Terms to Your New Programs
+           How to Apply These Terms to Your New Programs
 
   If you develop a new program, and you want it to be of the greatest
 possible use to the public, the best way to achieve this is to make it
@@ -672,169 +853,3 @@ may consider it more useful to permit linking proprietary applications with
 the library.  If this is what you want to do, use the GNU Lesser General
 Public License instead of this License.  But first, please read
 <http://www.gnu.org/philosophy/why-not-lgpl.html>.
-
-                   GNU LESSER GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-
-  This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
-  0. Additional Definitions.
-
-  As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
-  "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
-  An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
-  A "Combined Work" is a work produced by combining or linking an
-Application with the Library.  The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
-  The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
-  The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
-  1. Exception to Section 3 of the GNU GPL.
-
-  You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
-  2. Conveying Modified Versions.
-
-  If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
-   a) under this License, provided that you make a good faith effort to
-   ensure that, in the event an Application does not supply the
-   function or data, the facility still operates, and performs
-   whatever part of its purpose remains meaningful, or
-
-   b) under the GNU GPL, with none of the additional permissions of
-   this License applicable to that copy.
-
-  3. Object Code Incorporating Material from Library Header Files.
-
-  The object code form of an Application may incorporate material from
-a header file that is part of the Library.  You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
-   a) Give prominent notice with each copy of the object code that the
-   Library is used in it and that the Library and its use are
-   covered by this License.
-
-   b) Accompany the object code with a copy of the GNU GPL and this license
-   document.
-
-  4. Combined Works.
-
-  You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
-   a) Give prominent notice with each copy of the Combined Work that
-   the Library is used in it and that the Library and its use are
-   covered by this License.
-
-   b) Accompany the Combined Work with a copy of the GNU GPL and this license
-   document.
-
-   c) For a Combined Work that displays copyright notices during
-   execution, include the copyright notice for the Library among
-   these notices, as well as a reference directing the user to the
-   copies of the GNU GPL and this license document.
-
-   d) Do one of the following:
-
-       0) Convey the Minimal Corresponding Source under the terms of this
-       License, and the Corresponding Application Code in a form
-       suitable for, and under terms that permit, the user to
-       recombine or relink the Application with a modified version of
-       the Linked Version to produce a modified Combined Work, in the
-       manner specified by section 6 of the GNU GPL for conveying
-       Corresponding Source.
-
-       1) Use a suitable shared library mechanism for linking with the
-       Library.  A suitable mechanism is one that (a) uses at run time
-       a copy of the Library already present on the user's computer
-       system, and (b) will operate properly with a modified version
-       of the Library that is interface-compatible with the Linked
-       Version.
-
-   e) Provide Installation Information, but only if you would otherwise
-   be required to provide such information under section 6 of the
-   GNU GPL, and only to the extent that such information is
-   necessary to install and execute a modified version of the
-   Combined Work produced by recombining or relinking the
-   Application with a modified version of the Linked Version. (If
-   you use option 4d0, the Installation Information must accompany
-   the Minimal Corresponding Source and Corresponding Application
-   Code. If you use option 4d1, you must provide the Installation
-   Information in the manner specified by section 6 of the GNU GPL
-   for conveying Corresponding Source.)
-
-  5. Combined Libraries.
-
-  You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
-   a) Accompany the combined library with a copy of the same work based
-   on the Library, uncombined with any other library facilities,
-   conveyed under the terms of this License.
-
-   b) Give prominent notice with the combined library that part of it
-   is a work based on the Library, and explaining where to find the
-   accompanying uncombined form of the same work.
-
-  6. Revised Versions of the GNU Lesser General Public License.
-
-  The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
-  Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
-  If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
index 60734e65b63f445a0efd32451d39c42fe17d0272..9ffed93744fd9fb9127e5991e32e0f397583a809 100644 (file)
--- a/README.md
+++ b/README.md
@@ -30,10 +30,19 @@ Three backends are available:
   which is set in jexer.session.SwingSession.  For the demo
   application, this is the default backend on Windows platforms.
 
-The demo application showing the existing UI controls is available via
-'java -jar jexer.jar', 'java -Djexer.Swing=true -jar jexer.jar', or
-'java -cp jexer.jar jexer.demos.Demo2 PORT' (where PORT is a number to
-run the TCP daemon on).
+The demo application showing the existing UI controls can be seen in
+three ways:
+
+  * 'java -jar jexer.jar' .  This will use System.in/out on
+    non-Windows, or Swing on Windows.
+
+  * 'java -Djexer.Swing=true -jar jexer.jar' .  This will always use
+    Swing.
+
+  * 'java -cp jexer.jar jexer.demos.Demo2 PORT' (where PORT is a
+    number to run the TCP daemon on).  This will use the telnet
+    protocol to establish an 8-bit clean channel and be aware of
+    screen size changes.
 
 Additional backends can be created by subclassing
 jexer.backend.Backend and passing it into the TApplication
index 9bf6d0a9aaa5473511f1152b3b257bd6da561dcd..e4ee897cef16aa1f8aee35fbad8f0381ae5df945 100644 (file)
--- a/build.xml
+++ b/build.xml
     <target name="compile">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}"
-              includeantruntime="false"/>
+              includeantruntime="false"
+              debug="on"
+              debuglevel="lines,vars,source"
+              />
     </target>
 
     <target name="jar" depends="compile">
index 2ebdd36690d67432fa090e3a1d830b48c54edd6c..1aafa3c41619a4864d0297f1e966818e39e939da 100644 (file)
@@ -1584,17 +1584,18 @@ public final class ECMA48Terminal implements Runnable {
                         for (int i = 0; i < rc; i++) {
                             int ch = readBuffer[i];
                             processChar(events, (char)ch);
-                            if (events.size() > 0) {
-                                // Add to the queue for the backend thread to
-                                // be able to obtain.
-                                synchronized (eventQueue) {
-                                    eventQueue.addAll(events);
-                                }
-                                synchronized (listener) {
-                                    listener.notifyAll();
-                                }
-                                events.clear();
+                        }
+                        getIdleEvents(events);
+                        if (events.size() > 0) {
+                            // Add to the queue for the backend thread to
+                            // be able to obtain.
+                            synchronized (eventQueue) {
+                                eventQueue.addAll(events);
+                            }
+                            synchronized (listener) {
+                                listener.notifyAll();
                             }
+                            events.clear();
                         }
                     }
                 } else {
index f409b7a6d17084fe021a9cbeb4920f1a087f6d4e..d8164a4567d6f5d15a88e49b4e2221ce024a7c61 100644 (file)
@@ -32,6 +32,7 @@ package jexer.net;
 
 import java.io.InputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -41,7 +42,8 @@ import static jexer.net.TelnetSocket.*;
 /**
  * TelnetInputStream works with TelnetSocket to perform the telnet protocol.
  */
-public final class TelnetInputStream extends InputStream implements SessionInfo {
+public final class TelnetInputStream extends InputStream
+        implements SessionInfo {
 
     /**
      * The root TelnetSocket that has my telnet protocol state.
@@ -94,6 +96,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
         readBuffer      = new byte[1024];
         readBufferStart = 0;
         readBufferEnd   = 0;
+        subnegBuffer    = new ArrayList<Byte>();
     }
 
     // SessionInfo interface --------------------------------------------------
@@ -186,6 +189,10 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * skipped over) from this input stream without blocking by the next
      * invocation of a method for this input stream.
      *
+     * @return an estimate of the number of bytes that can be read (or
+     * skipped over) from this input stream without blocking or 0 when it
+     * reaches the end of the input stream.
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public int available() throws IOException {
@@ -201,6 +208,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
     /**
      * Closes this input stream and releases any system resources associated
      * with the stream.
+     *
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public void close() throws IOException {
@@ -212,14 +221,20 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Marks the current position in this input stream.
+     *
+     * @param readLimit the maximum limit of bytes that can be read before
+     * the mark position becomes invalid
      */
     @Override
-    public void mark(int readlimit) {
+    public void mark(final int readLimit) {
         // Do nothing
     }
 
     /**
      * Tests if this input stream supports the mark and reset methods.
+     *
+     * @return true if this stream instance supports the mark and reset
+     * methods; false otherwise
      */
     @Override
     public boolean markSupported() {
@@ -228,6 +243,10 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Reads the next byte of data from the input stream.
+     *
+     * @return the next byte of data, or -1 if there is no more data because
+     * the end of the stream has been reached.
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public int read() throws IOException {
@@ -261,18 +280,32 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
     /**
      * Reads some number of bytes from the input stream and stores them into
      * the buffer array b.
+     *
+     * @param b the buffer into which the data is read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public int read(byte[] b) throws IOException {
+    public int read(final byte[] b) throws IOException {
         return read(b, 0, b.length);
     }
 
     /**
      * Reads up to len bytes of data from the input stream into an array of
      * bytes.
+     *
+     * @param b the buffer into which the data is read.
+     * @param off the start offset in array b at which the data is written.
+     * @param len the maximum number of bytes to read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public int read(byte[] b, int off, int len) throws IOException {
+    public int read(final byte[] b, final int off,
+        final int len) throws IOException {
+
         // The only time we can return 0 is if len is 0, as per the
         // InputStream contract.
         if (len == 0) {
@@ -300,7 +333,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
         // If we got something, return it.
         if (rc > 0) {
-            System.arraycopy(readBuffer, 0, b, off, len);
+            System.arraycopy(readBuffer, 0, b, off, rc);
             return rc;
         }
         // If we read 0, I screwed up big time.
@@ -312,7 +345,10 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Repositions this stream to the position at the time the mark method
-     * was last called on this input stream.
+     * was last called on this input stream.  This is not supported by
+     * TelnetInputStream, so IOException is always thrown.
+     *
+     * @throws IOException if this function is used
      */
     @Override
     public void reset() throws IOException {
@@ -321,9 +357,13 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Skips over and discards n bytes of data from this input stream.
+     *
+     * @param n the number of bytes to be skipped
+     * @return the actual number of bytes skipped
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public long skip(long n) throws IOException {
+    public long skip(final long n) throws IOException {
         if (n < 0) {
             return 0;
         }
@@ -335,15 +375,43 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     // Telnet protocol --------------------------------------------------------
 
+
+    /**
+     * When true, the last read byte from the remote side was IAC.
+     */
+    private boolean iac = false;
+
+    /**
+     * When true, we are in the middle of a DO/DONT/WILL/WONT negotiation.
+     */
+    private boolean dowill = false;
+
+    /**
+     * The telnet option being negotiated.
+     */
+    private int dowillType = 0;
+
+    /**
+     * When true, we are waiting to see the end of the sub-negotiation
+     * sequence.
+     */
+    private boolean subnegEnd = false;
+
+    /**
+     * When true, the last byte read from the remote side was CR.
+     */
+    private boolean readCR = false;
+
     /**
      * The subnegotiation buffer.
      */
-    private byte [] subnegBuffer;
+    private ArrayList<Byte> subnegBuffer;
 
     /**
      * For debugging, return a descriptive string for this telnet option.
      * These are pulled from: http://www.iana.org/assignments/telnet-options
      *
+     * @param option the telnet option byte
      * @return a string describing the telnet option code
      */
     private String optionString(final int option) {
@@ -415,6 +483,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      *
      * @param response a TELNET_DO/DONT/WILL/WONT byte
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void respond(final int response,
         final int option) throws IOException {
@@ -431,6 +500,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * Tell the remote side we WILL support an option.
      *
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void WILL(final int option) throws IOException {
         respond(TELNET_WILL, option);
@@ -440,6 +510,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * Tell the remote side we WON'T support an option.
      *
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void WONT(final int option) throws IOException {
         respond(TELNET_WONT, option);
@@ -449,6 +520,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * Tell the remote side we DO support an option.
      *
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void DO(final int option) throws IOException {
         respond(TELNET_DO, option);
@@ -458,6 +530,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * Tell the remote side we DON'T support an option.
      *
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void DONT(final int option) throws IOException {
         respond(TELNET_DONT, option);
@@ -468,6 +541,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      *
      * @param remoteQuery a TELNET_DO/DONT/WILL/WONT byte
      * @param option telnet option byte (binary mode, term type, etc.)
+     * @throws IOException if an I/O error occurs
      */
     private void refuse(final int remoteQuery,
         final int option) throws IOException {
@@ -480,10 +554,11 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
     }
 
     /**
-     * Build sub-negotiation packet (RFC 855)
+     * Build sub-negotiation packet (RFC 855).
      *
      * @param option telnet option
      * @param response output buffer of response bytes
+     * @throws IOException if an I/O error occurs
      */
     private void telnetSendSubnegResponse(final int option,
         final byte [] response) throws IOException {
@@ -500,6 +575,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Telnet option: Terminal Speed (RFC 1079).  Client side.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void telnetSendTerminalSpeed() throws IOException {
         byte [] response = {0, '3', '8', '4', '0', '0', ',',
@@ -509,6 +586,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Telnet option: Terminal Type (RFC 1091).  Client side.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void telnetSendTerminalType() throws IOException {
         byte [] response = {0, 'v', 't', '1', '0', '0' };
@@ -517,6 +596,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Telnet option: Terminal Type (RFC 1091).  Server side.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void requestTerminalType() throws IOException {
         byte [] response = new byte[1];
@@ -526,6 +607,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Telnet option: Terminal Speed (RFC 1079).  Server side.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void requestTerminalSpeed() throws IOException {
         byte [] response = new byte[1];
@@ -535,6 +618,8 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Telnet option: New Environment (RFC 1572).  Server side.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void requestEnvironment() throws IOException {
         byte [] response = new byte[1];
@@ -543,7 +628,12 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
     }
 
     /**
-     * Send the options we want to negotiate on:
+     * Send the options we want to negotiate on.
+     *
+     * <p>The options we use are:
+     *
+     * <p>
+     * <pre>
      *     Binary Transmission           RFC 856
      *     Suppress Go Ahead             RFC 858
      *     Negotiate About Window Size   RFC 1073
@@ -552,70 +642,75 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      *     New Environment               RFC 1572
      *
      * When run as a server:
-     *     Echo
+     *     Echo                          RFC 857
+     * </pre>
+     *
+     * @throws IOException if an I/O error occurs
      */
     void telnetSendOptions() throws IOException {
-        if (master.nvt.binaryMode == false) {
+        if (master.binaryMode == false) {
             // Binary Transmission: must ask both do and will
             DO(0);
             WILL(0);
         }
 
-        if (master.nvt.goAhead == true) {
+        if (master.goAhead == true) {
             // Suppress Go Ahead
             DO(3);
             WILL(3);
         }
 
         // Server only options
-        if (master.nvt.isServer == true) {
+        if (master.isServer == true) {
             // Enable Echo - I echo to them, they do not echo back to me.
             DONT(1);
             WILL(1);
 
-            if (master.nvt.doTermType == true) {
+            if (master.doTermType == true) {
                 // Terminal type - request it
                 DO(24);
             }
 
-            if (master.nvt.doTermSpeed == true) {
+            if (master.doTermSpeed == true) {
                 // Terminal speed - request it
                 DO(32);
             }
 
-            if (master.nvt.doNAWS == true) {
+            if (master.doNAWS == true) {
                 // NAWS - request it
                 DO(31);
             }
 
-            if (master.nvt.doEnvironment == true) {
+            if (master.doEnvironment == true) {
                 // Environment - request it
                 DO(39);
             }
 
         } else {
 
-            if (master.nvt.doTermType == true) {
+            if (master.doTermType == true) {
                 // Terminal type - request it
                 WILL(24);
             }
 
-            if (master.nvt.doTermSpeed == true) {
+            if (master.doTermSpeed == true) {
                 // Terminal speed - request it
                 WILL(32);
             }
 
-            if (master.nvt.doNAWS == true) {
+            if (master.doNAWS == true) {
                 // NAWS - request it
                 WILL(31);
             }
 
-            if (master.nvt.doEnvironment == true) {
+            if (master.doEnvironment == true) {
                 // Environment - request it
                 WILL(39);
             }
-
         }
+
+        // Push it all out
+        output.flush();
     }
 
     /**
@@ -633,13 +728,15 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
      * fails to handle ESC as defined in RFC 1572.
      */
     private void handleNewEnvironment() {
-        Map<StringBuilder, StringBuilder> newEnv = new TreeMap<StringBuilder, StringBuilder>();
+        Map<StringBuilder, StringBuilder> newEnv =
+                new TreeMap<StringBuilder, StringBuilder>();
+
         EnvState state = EnvState.INIT;
         StringBuilder name = new StringBuilder();
         StringBuilder value = new StringBuilder();
 
-        for (int i = 0; i < subnegBuffer.length; i++) {
-            byte b = subnegBuffer[i];
+        for (int i = 0; i < subnegBuffer.size(); i++) {
+            Byte b = subnegBuffer.get(i);
 
             switch (state) {
 
@@ -677,7 +774,7 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
                     value = new StringBuilder();
                 } else {
                     // Take it as an environment variable name/key byte
-                    name.append((char)b);
+                    name.append((char)b.byteValue());
                 }
 
                 break;
@@ -700,9 +797,13 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
                     name = new StringBuilder();
                 } else {
                     // Take it as an environment variable value byte
-                    value.append((char)b);
+                    value.append((char)b.byteValue());
                 }
                 break;
+
+            default:
+                throw new RuntimeException("Invalid state: " + state);
+
             }
         }
 
@@ -725,80 +826,84 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
 
     /**
      * Handle an option sub-negotiation.
+     *
+     * @throws IOException if an I/O error occurs
      */
     private void handleSubneg() throws IOException {
-        byte option;
+        Byte option;
 
         // Sanity check: there must be at least 1 byte in subnegBuffer
-        if (subnegBuffer.length < 1) {
+        if (subnegBuffer.size() < 1) {
             // Buffer too small: the other side is a broken telnetd, it did
             // not send the right sub-negotiation data.  Bail out now.
             return;
         }
-        option = subnegBuffer[0];
+        option = subnegBuffer.get(0);
 
         switch (option) {
 
         case 24:
             // Terminal Type
-            if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
+            if ((subnegBuffer.size() > 1) && (subnegBuffer.get(1) == 1)) {
                 // Server sent "SEND", we say "IS"
                 telnetSendTerminalType();
             }
-            if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 0)) {
+            if ((subnegBuffer.size() > 1) && (subnegBuffer.get(1) == 0)) {
                 // Client sent "IS", record it
                 StringBuilder terminalString = new StringBuilder();
-                for (int i = 2; i < subnegBuffer.length; i++) {
-                    terminalString.append((char)subnegBuffer[i]);
+                for (int i = 2; i < subnegBuffer.size(); i++) {
+                    terminalString.append((char)subnegBuffer.
+                        get(i).byteValue());
                 }
-                master.nvt.terminal = terminalString.toString();
+                master.terminalType = terminalString.toString();
             }
             break;
 
         case 32:
             // Terminal Speed
-            if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
+            if ((subnegBuffer.size() > 1) && (subnegBuffer.get(1) == 1)) {
                 // Server sent "SEND", we say "IS"
                 telnetSendTerminalSpeed();
             }
-            if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 0)) {
+            if ((subnegBuffer.size() > 1) && (subnegBuffer.get(1) == 0)) {
                 // Client sent "IS", record it
                 StringBuilder speedString = new StringBuilder();
-                for (int i = 2; i < subnegBuffer.length; i++) {
-                    speedString.append((char)subnegBuffer[i]);
+                for (int i = 2; i < subnegBuffer.size(); i++) {
+                    speedString.append((char)subnegBuffer.get(i).byteValue());
                 }
                 String termSpeed = speedString.toString();
+                master.terminalSpeed = speedString.toString();
             }
             break;
 
         case 31:
             // NAWS
-            if (subnegBuffer.length >= 5) {
+            if (subnegBuffer.size() >= 5) {
                 int i = 0;
 
                 i++;
-                if (subnegBuffer[i] == TELNET_IAC) {
+                if (subnegBuffer.get(i) == (byte)TELNET_IAC) {
                     i++;
                 }
-                windowWidth = subnegBuffer[i] * 256;
+                windowWidth = subnegBuffer.get(i) * 256;
 
                 i++;
-                if (subnegBuffer[i] == TELNET_IAC) {
+                if (subnegBuffer.get(i) == (byte)TELNET_IAC) {
                     i++;
                 }
-                windowWidth += subnegBuffer[i];
+                windowWidth += subnegBuffer.get(i);
 
                 i++;
-                if (subnegBuffer[i] == TELNET_IAC) {
+                if (subnegBuffer.get(i) == (byte)TELNET_IAC) {
                     i++;
                 }
-                windowHeight = subnegBuffer[i] * 256;
+                windowHeight = subnegBuffer.get(i) * 256;
 
                 i++;
-                if (subnegBuffer[i] == TELNET_IAC) {
+                if (subnegBuffer.get(i) == (byte)TELNET_IAC) {
                     i++;
                 }
-                windowHeight += subnegBuffer[i];
+                windowHeight += subnegBuffer.get(i);
             }
             break;
 
@@ -816,11 +921,406 @@ public final class TelnetInputStream extends InputStream implements SessionInfo
     /**
      * Reads up to len bytes of data from the input stream into an array of
      * bytes.
+     *
+     * @param buf the buffer into which the data is read.
+     * @param off the start offset in array b at which the data is written.
+     * @param len the maximum number of bytes to read.
+     * @return the total number of bytes read into the buffer, or -1 if there
+     * is no more data because the end of the stream has been reached.
+     * @throws IOException if an I/O error occurs
      */
-    private int readImpl(byte[] b, int off, int len) throws IOException {
+    private int readImpl(final byte[] buf, final int off,
+        final int len) throws IOException {
+
         assert (len > 0);
-        // TODO
-        return -1;
+
+        // The current writing position in buf.
+        int bufN = 0;
+
+        // We will keep trying to read() until we have something to return.
+        do {
+
+            // Read up to len bytes
+            byte [] buffer = new byte[len];
+            int bufferN = 0;
+
+            // Read some data from the other end
+            int rc = input.read(buffer);
+
+            // Check for EOF or error
+            if (rc > 0) {
+                // More data came in
+                bufferN = rc;
+            } else {
+                // EOF, just return it.
+                return rc;
+            }
+
+            // Loop through the read bytes
+            for (int i = 0; i < bufferN; i++) {
+                byte b = buffer[i];
+
+                if (subnegEnd == true) {
+                    // Looking for IAC SE to end this subnegotiation
+                    if (b == (byte)TELNET_SE) {
+                        if (iac == true) {
+                            iac = false;
+                            subnegEnd = false;
+                            handleSubneg();
+                        }
+                    } else if (b == (byte)TELNET_IAC) {
+                        if (iac == true) {
+                            // An argument to the subnegotiation option
+                            subnegBuffer.add((byte)TELNET_IAC);
+                        } else {
+                            iac = true;
+                        }
+                    } else {
+                        // An argument to the subnegotiation option
+                        subnegBuffer.add(b);
+                    }
+                    continue;
+                }
+
+                // Look for DO/DON'T/WILL/WON'T option
+                if (dowill == true) {
+
+                    // Look for option/
+                    switch (b) {
+
+                    case 0:
+                        // Binary Transmission
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use binary transmission, yay.
+                            master.binaryMode = true;
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for binary transmission.
+                            WILL(b);
+                            master.binaryMode = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // We're screwed, server won't do binary
+                            // transmission.
+                            master.binaryMode = false;
+                        } else {
+                            // Server demands NVT ASCII mode.
+                            master.binaryMode = false;
+                        }
+                        break;
+
+                    case 1:
+                        // Echo
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use echo, yay.
+                            master.echoMode = true;
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for echo.
+                            WILL(b);
+                            master.echoMode = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // We're screwed, server won't do echo.
+                            master.echoMode = false;
+                        } else {
+                            // Server demands no echo.
+                            master.echoMode = false;
+                        }
+                        break;
+
+                    case 3:
+                        // Suppress Go Ahead
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use suppress go-ahead, yay.
+                            master.goAhead = false;
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for suppress go-ahead.
+                            WILL(b);
+                            master.goAhead = false;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // We're screwed, server won't do suppress
+                            // go-ahead.
+                            master.goAhead = true;
+                        } else {
+                            // Server demands Go-Ahead mode.
+                            master.goAhead = true;
+                        }
+                        break;
+
+                    case 24:
+                        // Terminal Type - send what's in TERM
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use terminal type, yay.
+                            if (master.isServer
+                                && master.doTermType
+                            ) {
+                                requestTerminalType();
+                                master.doTermType = false;
+                            } else if (!master.isServer) {
+                                master.doTermType = true;
+                            }
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for terminal type.
+                            WILL(b);
+                            master.doTermType = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // We're screwed, server won't do terminal type.
+                            master.doTermType = false;
+                        } else {
+                            // Server will not listen to terminal type.
+                            master.doTermType = false;
+                        }
+                        break;
+
+                    case 31:
+                        // NAWS
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use NAWS, yay.
+                            master.doNAWS = true;
+                            // NAWS cannot be requested by the server, it is
+                            // only sent by the client.
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for NAWS.
+                            WILL(b);
+                            master.doNAWS = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // Server won't do NAWS.
+                            master.doNAWS = false;
+                        } else {
+                            // Server will not listen to NAWS.
+                            master.doNAWS = false;
+                        }
+                        break;
+
+                    case 32:
+                        // Terminal Speed
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use terminal speed, yay.
+                            if (master.isServer
+                                && master.doTermSpeed
+                            ) {
+                                requestTerminalSpeed();
+                                master.doTermSpeed = false;
+                            } else if (!master.isServer) {
+                                master.doTermSpeed = true;
+                            }
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for terminal speed.
+                            WILL(b);
+                            master.doTermSpeed = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // We're screwed, server won't do terminal speed.
+                            master.doTermSpeed = false;
+                        } else {
+                            // Server will not listen to terminal speed.
+                            master.doTermSpeed = false;
+                        }
+                        break;
+
+                    case 39:
+                        // New Environment
+                        if (dowillType == (byte)TELNET_WILL) {
+                            // Server will use NewEnvironment, yay.
+                            if (master.isServer
+                                && master.doEnvironment
+                            ) {
+                                requestEnvironment();
+                                master.doEnvironment = false;
+                            } else if (!master.isServer) {
+                                master.doEnvironment = true;
+                            }
+                        } else if (dowillType == (byte)TELNET_DO) {
+                            // Server asks for NewEnvironment.
+                            WILL(b);
+                            master.doEnvironment = true;
+                        } else if (dowillType == (byte)TELNET_WONT) {
+                            // Server won't do NewEnvironment.
+                            master.doEnvironment = false;
+                        } else {
+                            // Server will not listen to New Environment.
+                            master.doEnvironment = false;
+                        }
+                        break;
+
+
+                    default:
+                        // Other side asked for something we don't
+                        // understand.  Tell them we will not do this option.
+                        refuse(dowillType, b);
+                        break;
+                    }
+
+                    dowill = false;
+                    continue;
+                } // if (dowill == true)
+
+                // Perform read processing
+                if (b == (byte)TELNET_IAC) {
+
+                    // Telnet command
+                    if (iac == true) {
+                        // IAC IAC -> IAC
+                        buf[bufN++] = (byte)TELNET_IAC;
+                        iac = false;
+                    } else {
+                        iac = true;
+                    }
+                    continue;
+                } else {
+                    if (iac == true) {
+
+                        switch (b) {
+
+                        case (byte)TELNET_SE:
+                            // log.debug1(" END Sub-Negotiation");
+                            break;
+                        case (byte)TELNET_NOP:
+                            // log.debug1(" NOP");
+                            break;
+                        case (byte)TELNET_DM:
+                            // log.debug1(" Data Mark");
+                            break;
+                        case (byte)TELNET_BRK:
+                            // log.debug1(" Break");
+                            break;
+                        case (byte)TELNET_IP:
+                            // log.debug1(" Interrupt Process");
+                            break;
+                        case (byte)TELNET_AO:
+                            // log.debug1(" Abort Output");
+                            break;
+                        case (byte)TELNET_AYT:
+                            // log.debug1(" Are You There?");
+                            break;
+                        case (byte)TELNET_EC:
+                            // log.debug1(" Erase Character");
+                            break;
+                        case (byte)TELNET_EL:
+                            // log.debug1(" Erase Line");
+                            break;
+                        case (byte)TELNET_GA:
+                            // log.debug1(" Go Ahead");
+                            break;
+                        case (byte)TELNET_SB:
+                            // log.debug1(" START Sub-Negotiation");
+                            // From here we wait for the IAC SE
+                            subnegEnd = true;
+                            subnegBuffer.clear();
+                            break;
+                        case (byte)TELNET_WILL:
+                            // log.debug1(" WILL");
+                            dowill = true;
+                            dowillType = b;
+                            break;
+                        case (byte)TELNET_WONT:
+                            // log.debug1(" WON'T");
+                            dowill = true;
+                            dowillType = b;
+                            break;
+                        case (byte)TELNET_DO:
+                            // log.debug1(" DO");
+                            dowill = true;
+                            dowillType = b;
+
+                            if (master.binaryMode == true) {
+                                // log.debug1("Telnet DO in binary mode");
+                            }
+
+                            break;
+                        case (byte)TELNET_DONT:
+                            // log.debug1(" DON'T");
+                            dowill = true;
+                            dowillType = b;
+                            break;
+                        default:
+                            // This should be equivalent to IAC NOP
+                            // log.debug1("Will treat as IAC NOP");
+                            break;
+                        }
+                        iac = false;
+                        continue;
+
+                    } // if (iac == true)
+
+                    /*
+                     * All of the regular IAC processing is completed at this
+                     * point.  Now we need to handle the CR and CR LF cases.
+                     *
+                     * According to RFC 854, in NVT ASCII mode:
+                     *     Bare CR -> CR NUL
+                     *     CR LF -> CR LF
+                     *
+                     */
+                    if (master.binaryMode == false) {
+
+                        if (b == C_LF) {
+                            if (readCR == true) {
+                                // This is CR LF.  Send CR LF and turn the cr
+                                // flag off.
+                                buf[bufN++] = C_CR;
+                                buf[bufN++] = C_LF;
+                                readCR = false;
+                                continue;
+                            }
+                            // This is bare LF.  Send LF.
+                            buf[bufN++] = C_LF;
+                            continue;
+                        }
+
+                        if (b == C_NUL) {
+                            if (readCR == true) {
+                                // This is CR NUL.  Send CR and turn the cr
+                                // flag off.
+                                buf[bufN++] = C_CR;
+                                readCR = false;
+                                continue;
+                            }
+                            // This is bare NUL.  Send NUL.
+                            buf[bufN++] = C_NUL;
+                            continue;
+                        }
+
+                        if (b == C_CR) {
+                            if (readCR == true) {
+                                // This is CR CR.  Send a CR NUL and leave
+                                // the cr flag on.
+                                buf[bufN++] = C_CR;
+                                buf[bufN++] = C_NUL;
+                                continue;
+                            }
+                            // This is the first CR.  Set the cr flag.
+                            readCR = true;
+                            continue;
+                        }
+
+                        if (readCR == true) {
+                            // This was a bare CR in the stream.
+                            buf[bufN++] = C_CR;
+                            readCR = false;
+                        }
+
+                        // This is a regular character.  Pass it on.
+                        buf[bufN++] = b;
+                        continue;
+                    }
+
+                    /*
+                     * This is the case for any of:
+                     *
+                     *     1) A NVT ASCII character that isn't CR, LF, or
+                     *        NUL.
+                     *
+                     *     2) A NVT binary character.
+                     *
+                     * For all of these cases, we just pass the character on.
+                     */
+                    buf[bufN++] = b;
+
+                } // if (b == TELNET_IAC)
+
+            } // for (int i = 0; i < bufferN; i++)
+
+        } while (bufN == 0);
+
+        // Return bytes read
+        return bufN;
     }
 
 
index 98727277a8b47e6a5397d3ac3e77e131424edce8..1bc82951fd76b490b76226e758d1f51d828b70e4 100644 (file)
@@ -54,7 +54,7 @@ public final class TelnetOutputStream extends OutputStream {
      * @param master the master TelnetSocket
      * @param output the underlying socket's OutputStream
      */
-    TelnetOutputStream(TelnetSocket master, OutputStream output) {
+    TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
         this.master = master;
         this.output = output;
     }
@@ -64,6 +64,8 @@ public final class TelnetOutputStream extends OutputStream {
     /**
      * Closes this output stream and releases any system resources associated
      * with this stream.
+     *
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public void close() throws IOException {
@@ -76,16 +78,18 @@ public final class TelnetOutputStream extends OutputStream {
     /**
      * Flushes this output stream and forces any buffered output bytes to be
      * written out.
+     *
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public void flush() throws IOException {
-        if ((master.nvt.binaryMode == false) && (master.nvt.writeCR == true)) {
+        if ((master.binaryMode == false) && (writeCR == true)) {
             // The last byte sent to this.write() was a CR, which was never
             // actually sent.  So send the CR in ascii mode, then flush.
             // CR <anything> -> CR NULL
             output.write(master.C_CR);
             output.write(master.C_NUL);
-            master.nvt.writeCR = false;
+            writeCR = false;
         }
         output.flush();
     }
@@ -93,26 +97,39 @@ public final class TelnetOutputStream extends OutputStream {
     /**
      * Writes b.length bytes from the specified byte array to this output
      * stream.
+     *
+     * @param b the data.
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public void write(byte[] b) throws IOException {
+    public void write(final byte[] b) throws IOException {
         writeImpl(b, 0, b.length);
     }
 
     /**
      * Writes len bytes from the specified byte array starting at offset off
      * to this output stream.
+     *
+     * @param b the data.
+     * @param off the start offset in the data.
+     * @param len the number of bytes to write.
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public void write(byte[] b, int off, int len) throws IOException {
+    public void write(final byte[] b, final int off,
+        final int len) throws IOException {
+
         writeImpl(b, off, len);
     }
 
     /**
      * Writes the specified byte to this output stream.
+     *
+     * @param b the byte to write.
+     * @throws IOException if an I/O error occurs
      */
     @Override
-    public void write(int b) throws IOException {
+    public void write(final int b) throws IOException {
         byte [] bytes = new byte[1];
         bytes[0] = (byte)b;
         writeImpl(bytes, 0, 1);
@@ -121,14 +138,29 @@ public final class TelnetOutputStream extends OutputStream {
     /**
      * Writes b.length bytes from the specified byte array to this output
      * stream.  Note package private access.
+     *
+     * @param b the data.
+     * @throws IOException if an I/O error occurs
      */
-    void rawWrite(byte[] b) throws IOException {
+    void rawWrite(final byte[] b) throws IOException {
         output.write(b, 0, b.length);
     }
 
+    // Telnet protocol --------------------------------------------------------
+
+    /**
+     * When true, the last byte the caller passed to write() was a CR.
+     */
+    private boolean writeCR = false;
+
     /**
      * Writes len bytes from the specified byte array starting at offset off
      * to this output stream.
+     *
+     * @param b the data.
+     * @param off the start offset in the data.
+     * @param len the number of bytes to write.
+     * @throws IOException if an I/O error occurs
      */
     private void writeImpl(final byte[] b, final int off,
         final int len) throws IOException {
@@ -141,14 +173,14 @@ public final class TelnetOutputStream extends OutputStream {
                 // Flush what we have generated so far and reset the buffer,
                 // because the next byte could generate up to 4 output bytes
                 // (CR <something> <IAC> <IAC>).
-                super.write(writeBuffer, 0, writeBufferI);
+                output.write(writeBuffer, 0, writeBufferI);
                 writeBufferI = 0;
             }
 
             // Pull the next byte
             byte ch = b[i + off];
 
-            if (master.nvt.binaryMode == true) {
+            if (master.binaryMode == true) {
 
                 if (ch == master.TELNET_IAC) {
                     // IAC -> IAC IAC
@@ -166,39 +198,39 @@ public final class TelnetOutputStream extends OutputStream {
 
             // Bare carriage return -> CR NUL
             if (ch == master.C_CR) {
-                if (master.nvt.writeCR == true) {
+                if (writeCR == true) {
                     // Flush the previous CR to the stream.
                     // CR <anything> -> CR NULL
                     writeBuffer[writeBufferI++] = (byte)master.C_CR;
                     writeBuffer[writeBufferI++] = (byte)master.C_NUL;
                 }
-                master.nvt.writeCR = true;
+                writeCR = true;
             } else if (ch == master.C_LF) {
-                if (master.nvt.writeCR == true) {
+                if (writeCR == true) {
                     // CR LF -> CR LF
                     writeBuffer[writeBufferI++] = (byte)master.C_CR;
                     writeBuffer[writeBufferI++] = (byte)master.C_LF;
-                    master.nvt.writeCR = false;
+                    writeCR = false;
                 } else {
                     // Bare LF -> LF
                     writeBuffer[writeBufferI++] = ch;
                 }
             } else if (ch == master.TELNET_IAC) {
-                if (master.nvt.writeCR == true) {
+                if (writeCR == true) {
                     // CR <anything> -> CR NULL
                     writeBuffer[writeBufferI++] = (byte)master.C_CR;
                     writeBuffer[writeBufferI++] = (byte)master.C_NUL;
-                    master.nvt.writeCR = false;
+                    writeCR = false;
                 }
                 // IAC -> IAC IAC
                 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
                 writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC;
             } else {
-                if (master.nvt.writeCR == true) {
+                if (writeCR == true) {
                     // CR <anything> -> CR NULL
                     writeBuffer[writeBufferI++] = (byte)master.C_CR;
                     writeBuffer[writeBufferI++] = (byte)master.C_NUL;
-                    master.nvt.writeCR = false;
+                    writeCR = false;
                 } else {
                     // Normal character */
                     writeBuffer[writeBufferI++] = ch;
@@ -209,9 +241,8 @@ public final class TelnetOutputStream extends OutputStream {
 
         if (writeBufferI > 0) {
             // Flush what we have generated so far and reset the buffer.
-            super.write(writeBuffer, 0, writeBufferI);
+            output.write(writeBuffer, 0, writeBufferI);
         }
     }
 
-
 }
index ed3975cad633ebd9027fe17cdd87e0f65f12b8fa..57e0b23383b78d223b579eb293cdd15b03a5f295 100644 (file)
  */
 package jexer.net;
 
-import java.io.InputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketException;
 
 /**
  * This class provides a ServerSocket that return TelnetSocket's in accept().
@@ -46,6 +45,8 @@ public final class TelnetServerSocket extends ServerSocket {
 
     /**
      * Creates an unbound server socket.
+     *
+     * @throws IOException if an I/O error occurs
      */
     public TelnetServerSocket() throws IOException {
         super();
@@ -53,6 +54,10 @@ public final class TelnetServerSocket extends ServerSocket {
 
     /**
      * Creates a server socket, bound to the specified port.
+     *
+     * @param port the port number, or 0 to use a port number that is
+     * automatically allocated.
+     * @throws IOException if an I/O error occurs
      */
     public TelnetServerSocket(final int port) throws IOException {
         super(port);
@@ -61,6 +66,12 @@ public final class TelnetServerSocket extends ServerSocket {
     /**
      * Creates a server socket and binds it to the specified local port
      * number, with the specified backlog.
+     *
+     * @param port the port number, or 0 to use a port number that is
+     * automatically allocated.
+     * @param backlog requested maximum length of the queue of incoming
+     * connections.
+     * @throws IOException if an I/O error occurs
      */
     public TelnetServerSocket(final int port,
         final int backlog) throws IOException {
@@ -71,6 +82,13 @@ public final class TelnetServerSocket extends ServerSocket {
     /**
      * Create a server with the specified port, listen backlog, and local IP
      * address to bind to.
+     *
+     * @param port the port number, or 0 to use a port number that is
+     * automatically allocated.
+     * @param backlog requested maximum length of the queue of incoming
+     * connections.
+     * @param bindAddr the local InetAddress the server will bind to
+     * @throws IOException if an I/O error occurs
      */
     public TelnetServerSocket(final int port, final int backlog,
         final InetAddress bindAddr) throws IOException {
@@ -81,11 +99,22 @@ public final class TelnetServerSocket extends ServerSocket {
     /**
      * Listens for a connection to be made to this socket and accepts it. The
      * method blocks until a connection is made.
+     *
+     * @return the new Socket
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public Socket accept() throws IOException {
-        Socket socket = super.accept();
-        return new TelnetSocket(socket);
+        if (isClosed()) {
+            throw new SocketException("Socket is closed");
+        }
+        if (!isBound()) {
+            throw new SocketException("Socket is not bound");
+        }
+
+        Socket socket = new TelnetSocket();
+        implAccept(socket);
+        return socket;
     }
-    
+
 }
index 2724e54953409ff83670a1c6adb2cddc40f8bf19..4a169de86ed9e2dfcead4f20d458128ca1f9d8e9 100644 (file)
@@ -42,110 +42,91 @@ import java.net.Socket;
  */
 public final class TelnetSocket extends Socket {
 
-    /**
-     * The wrapped socket.
-     */
-    private Socket socket;
-
     /**
      * The telnet-aware socket InputStream.
      */
     private TelnetInputStream input;
 
     /**
-     * The telnet-aware socket OutputStream. Note package private access:
-     * input sends stuff to output.
+     * The telnet-aware socket OutputStream.
      */
-    TelnetOutputStream output;
+    private TelnetOutputStream output;
 
     // Telnet protocol special characters.  Note package private access.
-    public static final int TELNET_SE         = 240;
-    public static final int TELNET_NOP        = 241;
-    public static final int TELNET_DM         = 242;
-    public static final int TELNET_BRK        = 243;
-    public static final int TELNET_IP         = 244;
-    public static final int TELNET_AO         = 245;
-    public static final int TELNET_AYT        = 246;
-    public static final int TELNET_EC         = 247;
-    public static final int TELNET_EL         = 248;
-    public static final int TELNET_GA         = 249;
-    public static final int TELNET_SB         = 250;
-    public static final int TELNET_WILL       = 251;
-    public static final int TELNET_WONT       = 252;
-    public static final int TELNET_DO         = 253;
-    public static final int TELNET_DONT       = 254;
-    public static final int TELNET_IAC        = 255;
-    public static final int C_NUL             = 0x00;
-    public static final int C_LF              = 0x0A;
-    public static final int C_CR              = 0x0D;
-
-    /**
-     * Telnet protocol speaks to a Network Virtual Terminal (NVT).
-     */
-    class TelnetState {
-
-        // General status flags outside the NVT spec
-        boolean doInit;
-        boolean isServer;
-
-        // NVT flags
-        boolean echoMode;
-        boolean binaryMode;
-        boolean goAhead;
-        boolean doTermType;
-        boolean doTermSpeed;
-        boolean doNAWS;
-        boolean doEnvironment;
-        String terminal = "";
-
-        // Flags used by the TelnetInputStream
-        boolean iac;
-        boolean dowill;
-        int dowillType;
-        boolean subnegEnd;
-        boolean isEof;
-        boolean eofMsg;
-        boolean readCR;
-
-        // Flags used by the TelnetOutputStream
-       boolean writeCR;
-
-        /**
-         * Constuctor calls reset().
-         */
-        public TelnetState() {
-            reset();
-        }
+    static final int TELNET_SE         = 240;
+    static final int TELNET_NOP        = 241;
+    static final int TELNET_DM         = 242;
+    static final int TELNET_BRK        = 243;
+    static final int TELNET_IP         = 244;
+    static final int TELNET_AO         = 245;
+    static final int TELNET_AYT        = 246;
+    static final int TELNET_EC         = 247;
+    static final int TELNET_EL         = 248;
+    static final int TELNET_GA         = 249;
+    static final int TELNET_SB         = 250;
+    static final int TELNET_WILL       = 251;
+    static final int TELNET_WONT       = 252;
+    static final int TELNET_DO         = 253;
+    static final int TELNET_DONT       = 254;
+    static final int TELNET_IAC        = 255;
+    static final int C_NUL             = 0x00;
+    static final int C_LF              = 0x0A;
+    static final int C_CR              = 0x0D;
 
-        /**
-         * Reset NVT to default state as per RFC 854.
-         */
-        public void reset() {
-            echoMode            = false;
-            binaryMode          = false;
-            goAhead             = true;
-            doTermType          = true;
-            doTermSpeed         = true;
-            doNAWS              = true;
-            doEnvironment       = true;
-            doInit              = true;
-            isServer            = true;
-
-            iac                 = false;
-            dowill              = false;
-            subnegEnd           = false;
-            isEof               = false;
-            eofMsg              = false;
-            readCR              = false;
-
-            writeCR             = false;
-        }
-    }
+    /**
+     * If true, this is a server socket (i.e. created by accept()).
+     */
+    boolean isServer = true;
+
+    /**
+     * If true, telnet ECHO mode is set such that local echo is off and
+     * remote echo is on.  This is appropriate for server sockets.
+     */
+    boolean echoMode = false;
+
+    /**
+     * If true, telnet BINARY mode is enabled.  We always want this to
+     * ensure a Unicode-safe stream.
+     */
+    boolean binaryMode = false;
+
+    /**
+     * If true, the SUPPRESS-GO-AHEAD option is enabled.  We always want
+     * this.
+     */
+    boolean goAhead = true;
+
+    /**
+     * If true, request the client terminal type.
+     */
+    boolean doTermType = true;
+
+    /**
+     * If true, request the client terminal speed.
+     */
+    boolean doTermSpeed = true;
+
+    /**
+     * If true, request the Negotiate About Window Size option to
+     * determine the client text width/height.
+     */
+    boolean doNAWS = true;
+
+    /**
+     * If true, request the New Environment option to obtain the client
+     * LOGNAME, USER, and LANG variables.
+     */
+    boolean doEnvironment;
+
+    /**
+     * The terminal type reported by the client.
+     */
+    String terminalType = "";
 
     /**
-     * State of the protocol.  Note package private access.
+     * The terminal speed reported by the client.
      */
-    TelnetState nvt;
+    String terminalSpeed = "";
 
     /**
      * See if telnet server/client is in ASCII mode.
@@ -153,24 +134,17 @@ public final class TelnetSocket extends Socket {
      * @return if true, this connection is in ASCII mode
      */
     public boolean isAscii() {
-        return (!nvt.binaryMode);
+        return (!binaryMode);
     }
 
     /**
-     * Creates a Socket that knows the telnet protocol.
+     * Creates a Socket that knows the telnet protocol.  Note package private
+     * access, this is only used by TelnetServerSocket.
      *
-     * @param socket the underlying Socket
+     * @throws IOException if an I/O error occurs
      */
-    TelnetSocket(Socket socket) throws IOException {
+    TelnetSocket() throws IOException {
         super();
-        nvt = new TelnetState();
-        this.socket = socket;
-
-        output = new TelnetOutputStream(this, super.getOutputStream());
-        input = new TelnetInputStream(this, super.getInputStream(), output);
-
-        // Initiate the telnet protocol negotiation.
-        input.telnetSendOptions();
     }
 
     // Socket interface -------------------------------------------------------
@@ -179,9 +153,16 @@ public final class TelnetSocket extends Socket {
      * Returns an input stream for this socket.
      *
      * @return the input stream
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public InputStream getInputStream() throws IOException {
+        if (input == null) {
+            assert (output == null);
+            output = new TelnetOutputStream(this, super.getOutputStream());
+            input = new TelnetInputStream(this, super.getInputStream(), output);
+            input.telnetSendOptions();
+        }
         return input;
     }
 
@@ -189,9 +170,16 @@ public final class TelnetSocket extends Socket {
      * Returns an output stream for this socket.
      *
      * @return the output stream
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public OutputStream getOutputStream() throws IOException {
+        if (output == null) {
+            assert (input == null);
+            output = new TelnetOutputStream(this, super.getOutputStream());
+            input = new TelnetInputStream(this, super.getInputStream(), output);
+            input.telnetSendOptions();
+        }
         return output;
     }
 
diff --git a/src/jexer/net/package-info.java b/src/jexer/net/package-info.java
new file mode 100644 (file)
index 0000000..b23492e
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * License: LGPLv3 or later
+ *
+ * This module is licensed under the GNU Lesser General Public License
+ * Version 3.  Please see the file "COPYING" in this directory for more
+ * information about the GNU Lesser General Public License Version 3.
+ *
+ *     Copyright (C) 2015  Kevin Lamonte
+ *
+ * This program 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
+ * 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/, or write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ * @author Kevin Lamonte [kevin.lamonte@gmail.com]
+ * @version 1
+ */
+
+/**
+ * A Telnet-aware ServerSocket that establishes an 8-bit clean data channel.
+ */
+package jexer.net;