Commit | Line | Data |
---|---|---|
daa4106c | 1 | /* |
ea91242c KL |
2 | * Jexer - Java Text User Interface |
3 | * | |
e16dda65 | 4 | * The MIT License (MIT) |
ea91242c | 5 | * |
a2018e99 | 6 | * Copyright (C) 2017 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 | */ | |
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 | */ | |
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 | } |