Commit | Line | Data |
---|---|---|
ea91242c KL |
1 | /** |
2 | * Jexer - Java Text User Interface | |
3 | * | |
4 | * License: LGPLv3 or later | |
5 | * | |
6 | * This module is licensed under the GNU Lesser General Public License | |
7 | * Version 3. Please see the file "COPYING" in this directory for more | |
8 | * information about the GNU Lesser General Public License Version 3. | |
9 | * | |
10 | * Copyright (C) 2015 Kevin Lamonte | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or | |
13 | * modify it under the terms of the GNU Lesser General Public License | |
14 | * as published by the Free Software Foundation; either version 3 of | |
15 | * the License, or (at your option) any later version. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, but | |
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 | * General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU Lesser General Public | |
23 | * License along with this program; if not, see | |
24 | * http://www.gnu.org/licenses/, or write to the Free Software | |
25 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
26 | * 02110-1301 USA | |
27 | * | |
28 | * @author Kevin Lamonte [kevin.lamonte@gmail.com] | |
29 | * @version 1 | |
30 | */ | |
31 | package jexer.net; | |
32 | ||
33 | import java.io.OutputStream; | |
34 | import java.io.IOException; | |
35 | ||
0d47c546 KL |
36 | import static jexer.net.TelnetSocket.*; |
37 | ||
ea91242c KL |
38 | /** |
39 | * TelnetOutputStream works with TelnetSocket to perform the telnet protocol. | |
40 | */ | |
41 | public final class TelnetOutputStream extends OutputStream { | |
42 | ||
43 | /** | |
44 | * The root TelnetSocket that has my telnet protocol state. | |
45 | */ | |
46 | private TelnetSocket master; | |
47 | ||
48 | /** | |
49 | * The raw socket's OutputStream. | |
50 | */ | |
51 | private OutputStream output; | |
52 | ||
53 | /** | |
54 | * Package private constructor. | |
55 | * | |
56 | * @param master the master TelnetSocket | |
57 | * @param output the underlying socket's OutputStream | |
58 | */ | |
9b1afdde | 59 | TelnetOutputStream(final TelnetSocket master, final OutputStream output) { |
ea91242c KL |
60 | this.master = master; |
61 | this.output = output; | |
62 | } | |
63 | ||
64 | // OutputStream interface ------------------------------------------------- | |
65 | ||
66 | /** | |
67 | * Closes this output stream and releases any system resources associated | |
68 | * with this stream. | |
9b1afdde KL |
69 | * |
70 | * @throws IOException if an I/O error occurs | |
ea91242c KL |
71 | */ |
72 | @Override | |
73 | public void close() throws IOException { | |
005ec497 KL |
74 | if (output != null) { |
75 | output.close(); | |
76 | output = null; | |
77 | } | |
ea91242c KL |
78 | } |
79 | ||
80 | /** | |
81 | * Flushes this output stream and forces any buffered output bytes to be | |
82 | * written out. | |
9b1afdde KL |
83 | * |
84 | * @throws IOException if an I/O error occurs | |
ea91242c KL |
85 | */ |
86 | @Override | |
87 | public void flush() throws IOException { | |
9b1afdde | 88 | if ((master.binaryMode == false) && (writeCR == true)) { |
005ec497 KL |
89 | // The last byte sent to this.write() was a CR, which was never |
90 | // actually sent. So send the CR in ascii mode, then flush. | |
91 | // CR <anything> -> CR NULL | |
0d47c546 KL |
92 | output.write(C_CR); |
93 | output.write(C_NUL); | |
9b1afdde | 94 | writeCR = false; |
005ec497 KL |
95 | } |
96 | output.flush(); | |
ea91242c KL |
97 | } |
98 | ||
99 | /** | |
100 | * Writes b.length bytes from the specified byte array to this output | |
101 | * stream. | |
9b1afdde KL |
102 | * |
103 | * @param b the data. | |
104 | * @throws IOException if an I/O error occurs | |
ea91242c KL |
105 | */ |
106 | @Override | |
9b1afdde | 107 | public void write(final byte[] b) throws IOException { |
005ec497 | 108 | writeImpl(b, 0, b.length); |
ea91242c KL |
109 | } |
110 | ||
111 | /** | |
112 | * Writes len bytes from the specified byte array starting at offset off | |
113 | * to this output stream. | |
9b1afdde KL |
114 | * |
115 | * @param b the data. | |
116 | * @param off the start offset in the data. | |
117 | * @param len the number of bytes to write. | |
118 | * @throws IOException if an I/O error occurs | |
ea91242c KL |
119 | */ |
120 | @Override | |
9b1afdde KL |
121 | public void write(final byte[] b, final int off, |
122 | final int len) throws IOException { | |
123 | ||
005ec497 | 124 | writeImpl(b, off, len); |
ea91242c KL |
125 | } |
126 | ||
127 | /** | |
128 | * Writes the specified byte to this output stream. | |
9b1afdde KL |
129 | * |
130 | * @param b the byte to write. | |
131 | * @throws IOException if an I/O error occurs | |
ea91242c KL |
132 | */ |
133 | @Override | |
9b1afdde | 134 | public void write(final int b) throws IOException { |
005ec497 KL |
135 | byte [] bytes = new byte[1]; |
136 | bytes[0] = (byte)b; | |
137 | writeImpl(bytes, 0, 1); | |
ea91242c KL |
138 | } |
139 | ||
005ec497 KL |
140 | /** |
141 | * Writes b.length bytes from the specified byte array to this output | |
142 | * stream. Note package private access. | |
9b1afdde KL |
143 | * |
144 | * @param b the data. | |
145 | * @throws IOException if an I/O error occurs | |
005ec497 | 146 | */ |
9b1afdde | 147 | void rawWrite(final byte[] b) throws IOException { |
005ec497 KL |
148 | output.write(b, 0, b.length); |
149 | } | |
150 | ||
9b1afdde KL |
151 | // Telnet protocol -------------------------------------------------------- |
152 | ||
153 | /** | |
154 | * When true, the last byte the caller passed to write() was a CR. | |
155 | */ | |
156 | private boolean writeCR = false; | |
157 | ||
005ec497 KL |
158 | /** |
159 | * Writes len bytes from the specified byte array starting at offset off | |
160 | * to this output stream. | |
9b1afdde KL |
161 | * |
162 | * @param b the data. | |
163 | * @param off the start offset in the data. | |
164 | * @param len the number of bytes to write. | |
165 | * @throws IOException if an I/O error occurs | |
005ec497 KL |
166 | */ |
167 | private void writeImpl(final byte[] b, final int off, | |
168 | final int len) throws IOException { | |
169 | ||
170 | byte [] writeBuffer = new byte[Math.max(len, 4)]; | |
171 | int writeBufferI = 0; | |
172 | ||
173 | for (int i = 0; i < len; i++) { | |
174 | if (writeBufferI >= writeBuffer.length - 4) { | |
175 | // Flush what we have generated so far and reset the buffer, | |
176 | // because the next byte could generate up to 4 output bytes | |
177 | // (CR <something> <IAC> <IAC>). | |
9b1afdde | 178 | output.write(writeBuffer, 0, writeBufferI); |
005ec497 KL |
179 | writeBufferI = 0; |
180 | } | |
181 | ||
182 | // Pull the next byte | |
183 | byte ch = b[i + off]; | |
184 | ||
9b1afdde | 185 | if (master.binaryMode == true) { |
005ec497 | 186 | |
0d47c546 | 187 | if (ch == TELNET_IAC) { |
005ec497 | 188 | // IAC -> IAC IAC |
0d47c546 KL |
189 | writeBuffer[writeBufferI++] = (byte)TELNET_IAC; |
190 | writeBuffer[writeBufferI++] = (byte)TELNET_IAC; | |
005ec497 KL |
191 | } else { |
192 | // Anything else -> just send | |
193 | writeBuffer[writeBufferI++] = ch; | |
194 | } | |
195 | continue; | |
196 | } | |
197 | ||
198 | // Non-binary mode: more complicated. We use writeCR to handle | |
199 | // the case that the last byte of b was a CR. | |
200 | ||
201 | // Bare carriage return -> CR NUL | |
0d47c546 | 202 | if (ch == C_CR) { |
9b1afdde | 203 | if (writeCR == true) { |
005ec497 KL |
204 | // Flush the previous CR to the stream. |
205 | // CR <anything> -> CR NULL | |
0d47c546 KL |
206 | writeBuffer[writeBufferI++] = (byte)C_CR; |
207 | writeBuffer[writeBufferI++] = (byte)C_NUL; | |
005ec497 | 208 | } |
9b1afdde | 209 | writeCR = true; |
0d47c546 | 210 | } else if (ch == C_LF) { |
9b1afdde | 211 | if (writeCR == true) { |
005ec497 | 212 | // CR LF -> CR LF |
0d47c546 KL |
213 | writeBuffer[writeBufferI++] = (byte)C_CR; |
214 | writeBuffer[writeBufferI++] = (byte)C_LF; | |
9b1afdde | 215 | writeCR = false; |
005ec497 KL |
216 | } else { |
217 | // Bare LF -> LF | |
218 | writeBuffer[writeBufferI++] = ch; | |
219 | } | |
0d47c546 | 220 | } else if (ch == TELNET_IAC) { |
9b1afdde | 221 | if (writeCR == true) { |
005ec497 | 222 | // CR <anything> -> CR NULL |
0d47c546 KL |
223 | writeBuffer[writeBufferI++] = (byte)C_CR; |
224 | writeBuffer[writeBufferI++] = (byte)C_NUL; | |
9b1afdde | 225 | writeCR = false; |
005ec497 KL |
226 | } |
227 | // IAC -> IAC IAC | |
0d47c546 KL |
228 | writeBuffer[writeBufferI++] = (byte)TELNET_IAC; |
229 | writeBuffer[writeBufferI++] = (byte)TELNET_IAC; | |
005ec497 | 230 | } else { |
9b1afdde | 231 | if (writeCR == true) { |
005ec497 | 232 | // CR <anything> -> CR NULL |
0d47c546 KL |
233 | writeBuffer[writeBufferI++] = (byte)C_CR; |
234 | writeBuffer[writeBufferI++] = (byte)C_NUL; | |
9b1afdde | 235 | writeCR = false; |
005ec497 KL |
236 | } else { |
237 | // Normal character */ | |
238 | writeBuffer[writeBufferI++] = ch; | |
239 | } | |
240 | } | |
241 | ||
242 | } // while (i < userbuf.length) | |
243 | ||
244 | if (writeBufferI > 0) { | |
245 | // Flush what we have generated so far and reset the buffer. | |
9b1afdde | 246 | output.write(writeBuffer, 0, writeBufferI); |
005ec497 KL |
247 | } |
248 | } | |
249 | ||
ea91242c | 250 | } |