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 | ||
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 | |
90 | output.write(master.C_CR); | |
91 | output.write(master.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 KL |
184 | |
185 | if (ch == master.TELNET_IAC) { | |
186 | // IAC -> IAC IAC | |
187 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
188 | writeBuffer[writeBufferI++] = (byte)master.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 == master.C_CR) { | |
9b1afdde | 201 | if (writeCR == true) { |
005ec497 KL |
202 | // Flush the previous CR to the stream. |
203 | // CR <anything> -> CR NULL | |
204 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
205 | writeBuffer[writeBufferI++] = (byte)master.C_NUL; | |
206 | } | |
9b1afdde | 207 | writeCR = true; |
005ec497 | 208 | } else if (ch == master.C_LF) { |
9b1afdde | 209 | if (writeCR == true) { |
005ec497 KL |
210 | // CR LF -> CR LF |
211 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
212 | writeBuffer[writeBufferI++] = (byte)master.C_LF; | |
9b1afdde | 213 | writeCR = false; |
005ec497 KL |
214 | } else { |
215 | // Bare LF -> LF | |
216 | writeBuffer[writeBufferI++] = ch; | |
217 | } | |
218 | } else if (ch == master.TELNET_IAC) { | |
9b1afdde | 219 | if (writeCR == true) { |
005ec497 KL |
220 | // CR <anything> -> CR NULL |
221 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
222 | writeBuffer[writeBufferI++] = (byte)master.C_NUL; | |
9b1afdde | 223 | writeCR = false; |
005ec497 KL |
224 | } |
225 | // IAC -> IAC IAC | |
226 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
227 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
228 | } else { | |
9b1afdde | 229 | if (writeCR == true) { |
005ec497 KL |
230 | // CR <anything> -> CR NULL |
231 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
232 | writeBuffer[writeBufferI++] = (byte)master.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 | } |