2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2016 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.OutputStream
;
32 import java
.io
.IOException
;
34 import static jexer
.net
.TelnetSocket
.*;
37 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
39 public final class TelnetOutputStream
extends OutputStream
{
42 * The root TelnetSocket that has my telnet protocol state.
44 private TelnetSocket master
;
47 * The raw socket's OutputStream.
49 private OutputStream output
;
52 * Package private constructor.
54 * @param master the master TelnetSocket
55 * @param output the underlying socket's OutputStream
57 TelnetOutputStream(final TelnetSocket master
, final OutputStream output
) {
62 // OutputStream interface -------------------------------------------------
65 * Closes this output stream and releases any system resources associated
68 * @throws IOException if an I/O error occurs
71 public void close() throws IOException
{
79 * Flushes this output stream and forces any buffered output bytes to be
82 * @throws IOException if an I/O error occurs
85 public void flush() throws IOException
{
86 if ((master
.binaryMode
== false) && (writeCR
== true)) {
87 // The last byte sent to this.write() was a CR, which was never
88 // actually sent. So send the CR in ascii mode, then flush.
89 // CR <anything> -> CR NULL
98 * Writes b.length bytes from the specified byte array to this output
102 * @throws IOException if an I/O error occurs
105 public void write(final byte[] b
) throws IOException
{
106 writeImpl(b
, 0, b
.length
);
110 * Writes len bytes from the specified byte array starting at offset off
111 * to this output stream.
114 * @param off the start offset in the data.
115 * @param len the number of bytes to write.
116 * @throws IOException if an I/O error occurs
119 public void write(final byte[] b
, final int off
,
120 final int len
) throws IOException
{
122 writeImpl(b
, off
, len
);
126 * Writes the specified byte to this output stream.
128 * @param b the byte to write.
129 * @throws IOException if an I/O error occurs
132 public void write(final int b
) throws IOException
{
133 byte [] bytes
= new byte[1];
135 writeImpl(bytes
, 0, 1);
139 * Writes b.length bytes from the specified byte array to this output
140 * stream. Note package private access.
143 * @throws IOException if an I/O error occurs
145 void rawWrite(final byte[] b
) throws IOException
{
146 output
.write(b
, 0, b
.length
);
149 // Telnet protocol --------------------------------------------------------
152 * When true, the last byte the caller passed to write() was a CR.
154 private boolean writeCR
= false;
157 * Writes len bytes from the specified byte array starting at offset off
158 * to this output stream.
161 * @param off the start offset in the data.
162 * @param len the number of bytes to write.
163 * @throws IOException if an I/O error occurs
165 private void writeImpl(final byte[] b
, final int off
,
166 final int len
) throws IOException
{
168 byte [] writeBuffer
= new byte[Math
.max(len
, 4)];
169 int writeBufferI
= 0;
171 for (int i
= 0; i
< len
; i
++) {
172 if (writeBufferI
>= writeBuffer
.length
- 4) {
173 // Flush what we have generated so far and reset the buffer,
174 // because the next byte could generate up to 4 output bytes
175 // (CR <something> <IAC> <IAC>).
176 output
.write(writeBuffer
, 0, writeBufferI
);
180 // Pull the next byte
181 byte ch
= b
[i
+ off
];
183 if (master
.binaryMode
== true) {
185 if (ch
== TELNET_IAC
) {
187 writeBuffer
[writeBufferI
++] = (byte)TELNET_IAC
;
188 writeBuffer
[writeBufferI
++] = (byte)TELNET_IAC
;
190 // Anything else -> just send
191 writeBuffer
[writeBufferI
++] = ch
;
196 // Non-binary mode: more complicated. We use writeCR to handle
197 // the case that the last byte of b was a CR.
199 // Bare carriage return -> CR NUL
201 if (writeCR
== true) {
202 // Flush the previous CR to the stream.
203 // CR <anything> -> CR NULL
204 writeBuffer
[writeBufferI
++] = (byte)C_CR
;
205 writeBuffer
[writeBufferI
++] = (byte)C_NUL
;
208 } else if (ch
== C_LF
) {
209 if (writeCR
== true) {
211 writeBuffer
[writeBufferI
++] = (byte)C_CR
;
212 writeBuffer
[writeBufferI
++] = (byte)C_LF
;
216 writeBuffer
[writeBufferI
++] = ch
;
218 } else if (ch
== TELNET_IAC
) {
219 if (writeCR
== true) {
220 // CR <anything> -> CR NULL
221 writeBuffer
[writeBufferI
++] = (byte)C_CR
;
222 writeBuffer
[writeBufferI
++] = (byte)C_NUL
;
226 writeBuffer
[writeBufferI
++] = (byte)TELNET_IAC
;
227 writeBuffer
[writeBufferI
++] = (byte)TELNET_IAC
;
229 if (writeCR
== true) {
230 // CR <anything> -> CR NULL
231 writeBuffer
[writeBufferI
++] = (byte)C_CR
;
232 writeBuffer
[writeBufferI
++] = (byte)C_NUL
;
235 // Normal character */
236 writeBuffer
[writeBufferI
++] = ch
;
240 } // while (i < userbuf.length)
242 if (writeBufferI
> 0) {
243 // Flush what we have generated so far and reset the buffer.
244 output
.write(writeBuffer
, 0, writeBufferI
);