LICENSE CHANGED TO MIT
[nikiroo-utils.git] / src / jexer / net / TelnetOutputStream.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2016 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 final class TelnetOutputStream extends OutputStream {
40
41 /**
42 * The root TelnetSocket that has my telnet protocol state.
43 */
44 private TelnetSocket master;
45
46 /**
47 * The raw socket's OutputStream.
48 */
49 private OutputStream output;
50
51 /**
52 * Package private constructor.
53 *
54 * @param master the master TelnetSocket
55 * @param output the underlying socket's OutputStream
56 */
57 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
58 this.master = master;
59 this.output = output;
60 }
61
62 // OutputStream interface -------------------------------------------------
63
64 /**
65 * Closes this output stream and releases any system resources associated
66 * with this stream.
67 *
68 * @throws IOException if an I/O error occurs
69 */
70 @Override
71 public void close() throws IOException {
72 if (output != null) {
73 output.close();
74 output = null;
75 }
76 }
77
78 /**
79 * Flushes this output stream and forces any buffered output bytes to be
80 * written out.
81 *
82 * @throws IOException if an I/O error occurs
83 */
84 @Override
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
90 output.write(C_CR);
91 output.write(C_NUL);
92 writeCR = false;
93 }
94 output.flush();
95 }
96
97 /**
98 * Writes b.length bytes from the specified byte array to this output
99 * stream.
100 *
101 * @param b the data.
102 * @throws IOException if an I/O error occurs
103 */
104 @Override
105 public void write(final byte[] b) throws IOException {
106 writeImpl(b, 0, b.length);
107 }
108
109 /**
110 * Writes len bytes from the specified byte array starting at offset off
111 * to this output stream.
112 *
113 * @param b the data.
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
117 */
118 @Override
119 public void write(final byte[] b, final int off,
120 final int len) throws IOException {
121
122 writeImpl(b, off, len);
123 }
124
125 /**
126 * Writes the specified byte to this output stream.
127 *
128 * @param b the byte to write.
129 * @throws IOException if an I/O error occurs
130 */
131 @Override
132 public void write(final int b) throws IOException {
133 byte [] bytes = new byte[1];
134 bytes[0] = (byte)b;
135 writeImpl(bytes, 0, 1);
136 }
137
138 /**
139 * Writes b.length bytes from the specified byte array to this output
140 * stream. Note package private access.
141 *
142 * @param b the data.
143 * @throws IOException if an I/O error occurs
144 */
145 void rawWrite(final byte[] b) throws IOException {
146 output.write(b, 0, b.length);
147 }
148
149 // Telnet protocol --------------------------------------------------------
150
151 /**
152 * When true, the last byte the caller passed to write() was a CR.
153 */
154 private boolean writeCR = false;
155
156 /**
157 * Writes len bytes from the specified byte array starting at offset off
158 * to this output stream.
159 *
160 * @param b the data.
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
164 */
165 private void writeImpl(final byte[] b, final int off,
166 final int len) throws IOException {
167
168 byte [] writeBuffer = new byte[Math.max(len, 4)];
169 int writeBufferI = 0;
170
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);
177 writeBufferI = 0;
178 }
179
180 // Pull the next byte
181 byte ch = b[i + off];
182
183 if (master.binaryMode == true) {
184
185 if (ch == TELNET_IAC) {
186 // IAC -> IAC IAC
187 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
188 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
189 } else {
190 // Anything else -> just send
191 writeBuffer[writeBufferI++] = ch;
192 }
193 continue;
194 }
195
196 // Non-binary mode: more complicated. We use writeCR to handle
197 // the case that the last byte of b was a CR.
198
199 // Bare carriage return -> CR NUL
200 if (ch == C_CR) {
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;
206 }
207 writeCR = true;
208 } else if (ch == C_LF) {
209 if (writeCR == true) {
210 // CR LF -> CR LF
211 writeBuffer[writeBufferI++] = (byte)C_CR;
212 writeBuffer[writeBufferI++] = (byte)C_LF;
213 writeCR = false;
214 } else {
215 // Bare LF -> LF
216 writeBuffer[writeBufferI++] = ch;
217 }
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;
223 writeCR = false;
224 }
225 // IAC -> IAC IAC
226 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
227 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
228 } else {
229 if (writeCR == true) {
230 // CR <anything> -> CR NULL
231 writeBuffer[writeBufferI++] = (byte)C_CR;
232 writeBuffer[writeBufferI++] = (byte)C_NUL;
233 writeCR = false;
234 } else {
235 // Normal character */
236 writeBuffer[writeBufferI++] = ch;
237 }
238 }
239
240 } // while (i < userbuf.length)
241
242 if (writeBufferI > 0) {
243 // Flush what we have generated so far and reset the buffer.
244 output.write(writeBuffer, 0, writeBufferI);
245 }
246 }
247
248 }