Add 'src/jexer/' from commit 'cf01c92f5809a0732409e280fb0f32f27393618d'
[fanfix.git] / src / jexer / net / TelnetOutputStream.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.net;
30
31 import java.io.OutputStream;
32 import java.io.IOException;
33
34 import static jexer.net.TelnetSocket.*;
35
36 /**
37 * TelnetOutputStream works with TelnetSocket to perform the telnet protocol.
38 */
39 public class TelnetOutputStream extends OutputStream {
40
41 // ------------------------------------------------------------------------
42 // Variables --------------------------------------------------------------
43 // ------------------------------------------------------------------------
44
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
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
64 /**
65 * Package private constructor.
66 *
67 * @param master the master TelnetSocket
68 * @param output the underlying socket's OutputStream
69 */
70 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
71 this.master = master;
72 this.output = output;
73 }
74
75 // ------------------------------------------------------------------------
76 // OutputStrem ------------------------------------------------------------
77 // ------------------------------------------------------------------------
78
79 /**
80 * Closes this output stream and releases any system resources associated
81 * with this stream.
82 *
83 * @throws IOException if an I/O error occurs
84 */
85 @Override
86 public void close() throws IOException {
87 if (output != null) {
88 output.close();
89 output = null;
90 }
91 }
92
93 /**
94 * Flushes this output stream and forces any buffered output bytes to be
95 * written out.
96 *
97 * @throws IOException if an I/O error occurs
98 */
99 @Override
100 public void flush() throws IOException {
101 if ((master.binaryMode == false) && (writeCR == true)) {
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
105 output.write(C_CR);
106 output.write(C_NUL);
107 writeCR = false;
108 }
109 output.flush();
110 }
111
112 /**
113 * Writes b.length bytes from the specified byte array to this output
114 * stream.
115 *
116 * @param b the data.
117 * @throws IOException if an I/O error occurs
118 */
119 @Override
120 public void write(final byte[] b) throws IOException {
121 writeImpl(b, 0, b.length);
122 }
123
124 /**
125 * Writes len bytes from the specified byte array starting at offset off
126 * to this output stream.
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
132 */
133 @Override
134 public void write(final byte[] b, final int off,
135 final int len) throws IOException {
136
137 writeImpl(b, off, len);
138 }
139
140 /**
141 * Writes the specified byte to this output stream.
142 *
143 * @param b the byte to write.
144 * @throws IOException if an I/O error occurs
145 */
146 @Override
147 public void write(final int b) throws IOException {
148 byte [] bytes = new byte[1];
149 bytes[0] = (byte) b;
150 writeImpl(bytes, 0, 1);
151 }
152
153 // ------------------------------------------------------------------------
154 // TelnetOutputStrem ------------------------------------------------------
155 // ------------------------------------------------------------------------
156
157 /**
158 * Writes b.length bytes from the specified byte array to this output
159 * stream. Note package private access.
160 *
161 * @param b the data.
162 * @throws IOException if an I/O error occurs
163 */
164 void rawWrite(final byte[] b) throws IOException {
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.
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
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>).
188 output.write(writeBuffer, 0, writeBufferI);
189 writeBufferI = 0;
190 }
191
192 // Pull the next byte
193 byte ch = b[i + off];
194
195 if (master.binaryMode == true) {
196
197 if (ch == TELNET_IAC) {
198 // IAC -> IAC IAC
199 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
200 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
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
212 if (ch == C_CR) {
213 if (writeCR == true) {
214 // Flush the previous CR to the stream.
215 // CR <anything> -> CR NULL
216 writeBuffer[writeBufferI++] = (byte) C_CR;
217 writeBuffer[writeBufferI++] = (byte) C_NUL;
218 }
219 writeCR = true;
220 } else if (ch == C_LF) {
221 if (writeCR == true) {
222 // CR LF -> CR LF
223 writeBuffer[writeBufferI++] = (byte) C_CR;
224 writeBuffer[writeBufferI++] = (byte) C_LF;
225 writeCR = false;
226 } else {
227 // Bare LF -> LF
228 writeBuffer[writeBufferI++] = ch;
229 }
230 } else if (ch == TELNET_IAC) {
231 if (writeCR == true) {
232 // CR <anything> -> CR NULL
233 writeBuffer[writeBufferI++] = (byte) C_CR;
234 writeBuffer[writeBufferI++] = (byte) C_NUL;
235 writeCR = false;
236 }
237 // IAC -> IAC IAC
238 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
239 writeBuffer[writeBufferI++] = (byte) TELNET_IAC;
240 } else {
241 if (writeCR == true) {
242 // CR <anything> -> CR NULL
243 writeBuffer[writeBufferI++] = (byte) C_CR;
244 writeBuffer[writeBufferI++] = (byte) C_NUL;
245 writeCR = false;
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.
256 output.write(writeBuffer, 0, writeBufferI);
257 }
258 }
259
260 }