Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / net / TelnetOutputStream.java
CommitLineData
daa4106c 1/*
ea91242c
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
ea91242c 5 *
a69ed767 6 * Copyright (C) 2019 Kevin Lamonte
ea91242c 7 *
e16dda65
KL
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:
ea91242c 14 *
e16dda65
KL
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
ea91242c 17 *
e16dda65
KL
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.
ea91242c
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer.net;
30
31import java.io.OutputStream;
32import java.io.IOException;
33
0d47c546
KL
34import static jexer.net.TelnetSocket.*;
35
ea91242c
KL
36/**
37 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
38 */
051e2913 39public class TelnetOutputStream extends OutputStream {
ea91242c 40
d36057df
KL
41 // ------------------------------------------------------------------------
42 // Variables --------------------------------------------------------------
43 // ------------------------------------------------------------------------
44
ea91242c
KL
45 /**
46 * The root TelnetSocket that has my telnet protocol state.
47 */
48 private TelnetSocket master;
49
50 /**
51 * The raw socket's OutputStream.
52 */
53 private OutputStream output;
54
d36057df
KL
55 /**
56 * When true, the last byte the caller passed to write() was a CR.
57 */
58 private boolean writeCR = false;
59
60 // ------------------------------------------------------------------------
61 // Constructors -----------------------------------------------------------
62 // ------------------------------------------------------------------------
63
ea91242c
KL
64 /**
65 * Package private constructor.
66 *
67 * @param master the master TelnetSocket
68 * @param output the underlying socket's OutputStream
69 */
9b1afdde 70 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
ea91242c
KL
71 this.master = master;
72 this.output = output;
73 }
74
d36057df
KL
75 // ------------------------------------------------------------------------
76 // OutputStrem ------------------------------------------------------------
77 // ------------------------------------------------------------------------
ea91242c
KL
78
79 /**
80 * Closes this output stream and releases any system resources associated
81 * with this stream.
9b1afdde
KL
82 *
83 * @throws IOException if an I/O error occurs
ea91242c
KL
84 */
85 @Override
86 public void close() throws IOException {
005ec497
KL
87 if (output != null) {
88 output.close();
89 output = null;
90 }
ea91242c
KL
91 }
92
93 /**
94 * Flushes this output stream and forces any buffered output bytes to be
95 * written out.
9b1afdde
KL
96 *
97 * @throws IOException if an I/O error occurs
ea91242c
KL
98 */
99 @Override
100 public void flush() throws IOException {
9b1afdde 101 if ((master.binaryMode == false) && (writeCR == true)) {
005ec497
KL
102 // The last byte sent to this.write() was a CR, which was never
103 // actually sent. So send the CR in ascii mode, then flush.
104 // CR <anything> -> CR NULL
0d47c546
KL
105 output.write(C_CR);
106 output.write(C_NUL);
9b1afdde 107 writeCR = false;
005ec497
KL
108 }
109 output.flush();
ea91242c
KL
110 }
111
112 /**
113 * Writes b.length bytes from the specified byte array to this output
114 * stream.
9b1afdde
KL
115 *
116 * @param b the data.
117 * @throws IOException if an I/O error occurs
ea91242c
KL
118 */
119 @Override
9b1afdde 120 public void write(final byte[] b) throws IOException {
005ec497 121 writeImpl(b, 0, b.length);
ea91242c
KL
122 }
123
124 /**
125 * Writes len bytes from the specified byte array starting at offset off
126 * to this output stream.
9b1afdde
KL
127 *
128 * @param b the data.
129 * @param off the start offset in the data.
130 * @param len the number of bytes to write.
131 * @throws IOException if an I/O error occurs
ea91242c
KL
132 */
133 @Override
9b1afdde
KL
134 public void write(final byte[] b, final int off,
135 final int len) throws IOException {
136
005ec497 137 writeImpl(b, off, len);
ea91242c
KL
138 }
139
140 /**
141 * Writes the specified byte to this output stream.
9b1afdde
KL
142 *
143 * @param b the byte to write.
144 * @throws IOException if an I/O error occurs
ea91242c
KL
145 */
146 @Override
9b1afdde 147 public void write(final int b) throws IOException {
005ec497 148 byte [] bytes = new byte[1];
21a7aa8a 149 bytes[0] = (byte) b;
005ec497 150 writeImpl(bytes, 0, 1);
ea91242c
KL
151 }
152
d36057df
KL
153 // ------------------------------------------------------------------------
154 // TelnetOutputStrem ------------------------------------------------------
155 // ------------------------------------------------------------------------
156
005ec497
KL
157 /**
158 * Writes b.length bytes from the specified byte array to this output
159 * stream. Note package private access.
9b1afdde
KL
160 *
161 * @param b the data.
162 * @throws IOException if an I/O error occurs
005ec497 163 */
9b1afdde 164 void rawWrite(final byte[] b) throws IOException {
005ec497
KL
165 output.write(b, 0, b.length);
166 }
167
168 /**
169 * Writes len bytes from the specified byte array starting at offset off
170 * to this output stream.
9b1afdde
KL
171 *
172 * @param b the data.
173 * @param off the start offset in the data.
174 * @param len the number of bytes to write.
175 * @throws IOException if an I/O error occurs
005ec497
KL
176 */
177 private void writeImpl(final byte[] b, final int off,
178 final int len) throws IOException {
179
180 byte [] writeBuffer = new byte[Math.max(len, 4)];
181 int writeBufferI = 0;
182
183 for (int i = 0; i < len; i++) {
184 if (writeBufferI >= writeBuffer.length - 4) {
185 // Flush what we have generated so far and reset the buffer,
186 // because the next byte could generate up to 4 output bytes
187 // (CR <something> <IAC> <IAC>).
9b1afdde 188 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
189 writeBufferI = 0;
190 }
191
192 // Pull the next byte
193 byte ch = b[i + off];
194
9b1afdde 195 if (master.binaryMode == true) {
005ec497 196
0d47c546 197 if (ch == TELNET_IAC) {
005ec497 198 // IAC -> IAC IAC
21a7aa8a
KL
199 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
200 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
005ec497
KL
201 } else {
202 // Anything else -> just send
203 writeBuffer[writeBufferI++] = ch;
204 }
205 continue;
206 }
207
208 // Non-binary mode: more complicated. We use writeCR to handle
209 // the case that the last byte of b was a CR.
210
211 // Bare carriage return -> CR NUL
0d47c546 212 if (ch == C_CR) {
9b1afdde 213 if (writeCR == true) {
005ec497
KL
214 // Flush the previous CR to the stream.
215 // CR <anything> -> CR NULL
21a7aa8a
KL
216 writeBuffer[writeBufferI++] = (byte) C_CR;
217 writeBuffer[writeBufferI++] = (byte) C_NUL;
005ec497 218 }
9b1afdde 219 writeCR = true;
0d47c546 220 } else if (ch == C_LF) {
9b1afdde 221 if (writeCR == true) {
005ec497 222 // CR LF -> CR LF
21a7aa8a
KL
223 writeBuffer[writeBufferI++] = (byte) C_CR;
224 writeBuffer[writeBufferI++] = (byte) C_LF;
9b1afdde 225 writeCR = false;
005ec497
KL
226 } else {
227 // Bare LF -> LF
228 writeBuffer[writeBufferI++] = ch;
229 }
0d47c546 230 } else if (ch == TELNET_IAC) {
9b1afdde 231 if (writeCR == true) {
005ec497 232 // CR <anything> -> CR NULL
21a7aa8a
KL
233 writeBuffer[writeBufferI++] = (byte) C_CR;
234 writeBuffer[writeBufferI++] = (byte) C_NUL;
9b1afdde 235 writeCR = false;
005ec497
KL
236 }
237 // IAC -> IAC IAC
21a7aa8a
KL
238 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
239 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
005ec497 240 } else {
9b1afdde 241 if (writeCR == true) {
005ec497 242 // CR <anything> -> CR NULL
21a7aa8a
KL
243 writeBuffer[writeBufferI++] = (byte) C_CR;
244 writeBuffer[writeBufferI++] = (byte) C_NUL;
9b1afdde 245 writeCR = false;
005ec497
KL
246 } else {
247 // Normal character */
248 writeBuffer[writeBufferI++] = ch;
249 }
250 }
251
252 } // while (i < userbuf.length)
253
254 if (writeBufferI > 0) {
255 // Flush what we have generated so far and reset the buffer.
9b1afdde 256 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
257 }
258 }
259
ea91242c 260}