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 | */ | |
57 | TelnetOutputStream(TelnetSocket master, OutputStream output) { | |
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. | |
67 | */ | |
68 | @Override | |
69 | public void close() throws IOException { | |
005ec497 KL |
70 | if (output != null) { |
71 | output.close(); | |
72 | output = null; | |
73 | } | |
ea91242c KL |
74 | } |
75 | ||
76 | /** | |
77 | * Flushes this output stream and forces any buffered output bytes to be | |
78 | * written out. | |
79 | */ | |
80 | @Override | |
81 | public void flush() throws IOException { | |
005ec497 KL |
82 | if ((master.nvt.binaryMode == false) && (master.nvt.writeCR == true)) { |
83 | // The last byte sent to this.write() was a CR, which was never | |
84 | // actually sent. So send the CR in ascii mode, then flush. | |
85 | // CR <anything> -> CR NULL | |
86 | output.write(master.C_CR); | |
87 | output.write(master.C_NUL); | |
88 | master.nvt.writeCR = false; | |
89 | } | |
90 | output.flush(); | |
ea91242c KL |
91 | } |
92 | ||
93 | /** | |
94 | * Writes b.length bytes from the specified byte array to this output | |
95 | * stream. | |
96 | */ | |
97 | @Override | |
98 | public void write(byte[] b) throws IOException { | |
005ec497 | 99 | writeImpl(b, 0, b.length); |
ea91242c KL |
100 | } |
101 | ||
102 | /** | |
103 | * Writes len bytes from the specified byte array starting at offset off | |
104 | * to this output stream. | |
105 | */ | |
106 | @Override | |
107 | public void write(byte[] b, int off, int len) throws IOException { | |
005ec497 | 108 | writeImpl(b, off, len); |
ea91242c KL |
109 | } |
110 | ||
111 | /** | |
112 | * Writes the specified byte to this output stream. | |
113 | */ | |
114 | @Override | |
115 | public void write(int b) throws IOException { | |
005ec497 KL |
116 | byte [] bytes = new byte[1]; |
117 | bytes[0] = (byte)b; | |
118 | writeImpl(bytes, 0, 1); | |
ea91242c KL |
119 | } |
120 | ||
005ec497 KL |
121 | /** |
122 | * Writes b.length bytes from the specified byte array to this output | |
123 | * stream. Note package private access. | |
124 | */ | |
125 | void rawWrite(byte[] b) throws IOException { | |
126 | output.write(b, 0, b.length); | |
127 | } | |
128 | ||
129 | /** | |
130 | * Writes len bytes from the specified byte array starting at offset off | |
131 | * to this output stream. | |
132 | */ | |
133 | private void writeImpl(final byte[] b, final int off, | |
134 | final int len) throws IOException { | |
135 | ||
136 | byte [] writeBuffer = new byte[Math.max(len, 4)]; | |
137 | int writeBufferI = 0; | |
138 | ||
139 | for (int i = 0; i < len; i++) { | |
140 | if (writeBufferI >= writeBuffer.length - 4) { | |
141 | // Flush what we have generated so far and reset the buffer, | |
142 | // because the next byte could generate up to 4 output bytes | |
143 | // (CR <something> <IAC> <IAC>). | |
144 | super.write(writeBuffer, 0, writeBufferI); | |
145 | writeBufferI = 0; | |
146 | } | |
147 | ||
148 | // Pull the next byte | |
149 | byte ch = b[i + off]; | |
150 | ||
151 | if (master.nvt.binaryMode == true) { | |
152 | ||
153 | if (ch == master.TELNET_IAC) { | |
154 | // IAC -> IAC IAC | |
155 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
156 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
157 | } else { | |
158 | // Anything else -> just send | |
159 | writeBuffer[writeBufferI++] = ch; | |
160 | } | |
161 | continue; | |
162 | } | |
163 | ||
164 | // Non-binary mode: more complicated. We use writeCR to handle | |
165 | // the case that the last byte of b was a CR. | |
166 | ||
167 | // Bare carriage return -> CR NUL | |
168 | if (ch == master.C_CR) { | |
169 | if (master.nvt.writeCR == true) { | |
170 | // Flush the previous CR to the stream. | |
171 | // CR <anything> -> CR NULL | |
172 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
173 | writeBuffer[writeBufferI++] = (byte)master.C_NUL; | |
174 | } | |
175 | master.nvt.writeCR = true; | |
176 | } else if (ch == master.C_LF) { | |
177 | if (master.nvt.writeCR == true) { | |
178 | // CR LF -> CR LF | |
179 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
180 | writeBuffer[writeBufferI++] = (byte)master.C_LF; | |
181 | master.nvt.writeCR = false; | |
182 | } else { | |
183 | // Bare LF -> LF | |
184 | writeBuffer[writeBufferI++] = ch; | |
185 | } | |
186 | } else if (ch == master.TELNET_IAC) { | |
187 | if (master.nvt.writeCR == true) { | |
188 | // CR <anything> -> CR NULL | |
189 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
190 | writeBuffer[writeBufferI++] = (byte)master.C_NUL; | |
191 | master.nvt.writeCR = false; | |
192 | } | |
193 | // IAC -> IAC IAC | |
194 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
195 | writeBuffer[writeBufferI++] = (byte)master.TELNET_IAC; | |
196 | } else { | |
197 | if (master.nvt.writeCR == true) { | |
198 | // CR <anything> -> CR NULL | |
199 | writeBuffer[writeBufferI++] = (byte)master.C_CR; | |
200 | writeBuffer[writeBufferI++] = (byte)master.C_NUL; | |
201 | master.nvt.writeCR = false; | |
202 | } else { | |
203 | // Normal character */ | |
204 | writeBuffer[writeBufferI++] = ch; | |
205 | } | |
206 | } | |
207 | ||
208 | } // while (i < userbuf.length) | |
209 | ||
210 | if (writeBufferI > 0) { | |
211 | // Flush what we have generated so far and reset the buffer. | |
212 | super.write(writeBuffer, 0, writeBufferI); | |
213 | } | |
214 | } | |
215 | ||
216 | ||
ea91242c | 217 | } |