LICENSE CHANGED TO MIT
[nikiroo-utils.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 *
e16dda65 6 * Copyright (C) 2016 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 */
39public 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 */
9b1afdde 57 TelnetOutputStream(final TelnetSocket master, final OutputStream output) {
ea91242c
KL
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.
9b1afdde
KL
67 *
68 * @throws IOException if an I/O error occurs
ea91242c
KL
69 */
70 @Override
71 public void close() throws IOException {
005ec497
KL
72 if (output != null) {
73 output.close();
74 output = null;
75 }
ea91242c
KL
76 }
77
78 /**
79 * Flushes this output stream and forces any buffered output bytes to be
80 * written out.
9b1afdde
KL
81 *
82 * @throws IOException if an I/O error occurs
ea91242c
KL
83 */
84 @Override
85 public void flush() throws IOException {
9b1afdde 86 if ((master.binaryMode == false) && (writeCR == true)) {
005ec497
KL
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
0d47c546
KL
90 output.write(C_CR);
91 output.write(C_NUL);
9b1afdde 92 writeCR = false;
005ec497
KL
93 }
94 output.flush();
ea91242c
KL
95 }
96
97 /**
98 * Writes b.length bytes from the specified byte array to this output
99 * stream.
9b1afdde
KL
100 *
101 * @param b the data.
102 * @throws IOException if an I/O error occurs
ea91242c
KL
103 */
104 @Override
9b1afdde 105 public void write(final byte[] b) throws IOException {
005ec497 106 writeImpl(b, 0, b.length);
ea91242c
KL
107 }
108
109 /**
110 * Writes len bytes from the specified byte array starting at offset off
111 * to this output stream.
9b1afdde
KL
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
ea91242c
KL
117 */
118 @Override
9b1afdde
KL
119 public void write(final byte[] b, final int off,
120 final int len) throws IOException {
121
005ec497 122 writeImpl(b, off, len);
ea91242c
KL
123 }
124
125 /**
126 * Writes the specified byte to this output stream.
9b1afdde
KL
127 *
128 * @param b the byte to write.
129 * @throws IOException if an I/O error occurs
ea91242c
KL
130 */
131 @Override
9b1afdde 132 public void write(final int b) throws IOException {
005ec497
KL
133 byte [] bytes = new byte[1];
134 bytes[0] = (byte)b;
135 writeImpl(bytes, 0, 1);
ea91242c
KL
136 }
137
005ec497
KL
138 /**
139 * Writes b.length bytes from the specified byte array to this output
140 * stream. Note package private access.
9b1afdde
KL
141 *
142 * @param b the data.
143 * @throws IOException if an I/O error occurs
005ec497 144 */
9b1afdde 145 void rawWrite(final byte[] b) throws IOException {
005ec497
KL
146 output.write(b, 0, b.length);
147 }
148
9b1afdde
KL
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
005ec497
KL
156 /**
157 * Writes len bytes from the specified byte array starting at offset off
158 * to this output stream.
9b1afdde
KL
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
005ec497
KL
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>).
9b1afdde 176 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
177 writeBufferI = 0;
178 }
179
180 // Pull the next byte
181 byte ch = b[i + off];
182
9b1afdde 183 if (master.binaryMode == true) {
005ec497 184
0d47c546 185 if (ch == TELNET_IAC) {
005ec497 186 // IAC -> IAC IAC
0d47c546
KL
187 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
188 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
005ec497
KL
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
0d47c546 200 if (ch == C_CR) {
9b1afdde 201 if (writeCR == true) {
005ec497
KL
202 // Flush the previous CR to the stream.
203 // CR <anything> -> CR NULL
0d47c546
KL
204 writeBuffer[writeBufferI++] = (byte)C_CR;
205 writeBuffer[writeBufferI++] = (byte)C_NUL;
005ec497 206 }
9b1afdde 207 writeCR = true;
0d47c546 208 } else if (ch == C_LF) {
9b1afdde 209 if (writeCR == true) {
005ec497 210 // CR LF -> CR LF
0d47c546
KL
211 writeBuffer[writeBufferI++] = (byte)C_CR;
212 writeBuffer[writeBufferI++] = (byte)C_LF;
9b1afdde 213 writeCR = false;
005ec497
KL
214 } else {
215 // Bare LF -> LF
216 writeBuffer[writeBufferI++] = ch;
217 }
0d47c546 218 } else if (ch == TELNET_IAC) {
9b1afdde 219 if (writeCR == true) {
005ec497 220 // CR <anything> -> CR NULL
0d47c546
KL
221 writeBuffer[writeBufferI++] = (byte)C_CR;
222 writeBuffer[writeBufferI++] = (byte)C_NUL;
9b1afdde 223 writeCR = false;
005ec497
KL
224 }
225 // IAC -> IAC IAC
0d47c546
KL
226 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
227 writeBuffer[writeBufferI++] = (byte)TELNET_IAC;
005ec497 228 } else {
9b1afdde 229 if (writeCR == true) {
005ec497 230 // CR <anything> -> CR NULL
0d47c546
KL
231 writeBuffer[writeBufferI++] = (byte)C_CR;
232 writeBuffer[writeBufferI++] = (byte)C_NUL;
9b1afdde 233 writeCR = false;
005ec497
KL
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.
9b1afdde 244 output.write(writeBuffer, 0, writeBufferI);
005ec497
KL
245 }
246 }
247
ea91242c 248}