Commit | Line | Data |
---|---|---|
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 | */ | |
29 | package jexer.net; | |
30 | ||
31 | import java.io.OutputStream; | |
32 | import java.io.IOException; | |
33 | ||
0d47c546 KL |
34 | import static jexer.net.TelnetSocket.*; |
35 | ||
ea91242c KL |
36 | /** |
37 | * TelnetOutputStream works with TelnetSocket to perform the telnet protocol. | |
38 | */ | |
051e2913 | 39 | public 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 | } |