1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.net.telnet;
19
20 import java.io.IOException;
21 import java.io.OutputStream;
22
23 /**
24 * Wraps an output stream.
25 * <p>
26 * In binary mode, the only conversion is to double IAC.
27 * </p>
28 * <p>
29 * In ASCII mode, if convertCRtoCRLF is true (currently always true), any CR is converted to CRLF. IACs are doubled. Also, a bare LF is converted to CRLF and a
30 * bare CR is converted to CR\0
31 * </p>
32 */
33 final class TelnetOutputStream extends OutputStream {
34 private static final boolean CONVERT_TO_CRLF = true;
35 private final TelnetClient client;
36 // TODO there does not appear to be any way to change this value - should it be a ctor parameter?
37 private boolean lastWasCR;
38
39 TelnetOutputStream(final TelnetClient client) {
40 this.client = client;
41 }
42
43 /** Closes the stream. */
44 @Override
45 public void close() throws IOException {
46 client.closeOutputStream();
47 }
48
49 /** Flushes the stream. */
50 @Override
51 public void flush() throws IOException {
52 client.flushOutputStream();
53 }
54
55 /**
56 * Writes a byte array to the stream.
57 *
58 * @param buffer The byte array to write.
59 * @throws IOException If an error occurs while writing to the underlying stream.
60 */
61 @Override
62 public void write(final byte buffer[]) throws IOException {
63 write(buffer, 0, buffer.length);
64 }
65
66 /**
67 * Writes a number of bytes from a byte array to the stream starting from a given offset.
68 *
69 * @param buffer The byte array to write.
70 * @param offset The offset into the array at which to start copying data.
71 * @param length The number of bytes to write.
72 * @throws IOException If an error occurs while writing to the underlying stream.
73 */
74 @Override
75 public void write(final byte buffer[], int offset, int length) throws IOException {
76 synchronized (client) {
77 while (length-- > 0) {
78 write(buffer[offset++]);
79 }
80 }
81 }
82
83 /**
84 * Writes a byte to the stream.
85 *
86 * @param ch The byte to write.
87 * @throws IOException If an error occurs while writing to the underlying stream.
88 */
89 @Override
90 public void write(int ch) throws IOException {
91
92 synchronized (client) {
93 ch &= 0xff;
94
95 // i.e. ASCII
96 if (client.requestedWont(TelnetOption.BINARY)) {
97 if (lastWasCR) {
98 if (CONVERT_TO_CRLF) {
99 client.sendByte('\n');
100 if (ch == '\n') {
101 // i.e. was CRLF anyway
102 lastWasCR = false;
103 return;
104 }
105 } else if (ch != '\n') {
106 // convertCRtoCRLF
107 client.sendByte(Telnet.NUL); // RFC854 requires CR NUL for bare CR
108 }
109 }
110
111 switch (ch) {
112 case '\r':
113 client.sendByte('\r');
114 lastWasCR = true;
115 break;
116 case '\n':
117 if (!lastWasCR) { // convert LF to CRLF
118 client.sendByte('\r');
119 }
120 client.sendByte(ch);
121 lastWasCR = false;
122 break;
123 case TelnetCommand.IAC:
124 client.sendByte(TelnetCommand.IAC);
125 client.sendByte(TelnetCommand.IAC);
126 lastWasCR = false;
127 break;
128 default:
129 client.sendByte(ch);
130 lastWasCR = false;
131 break;
132 }
133 // end ASCII
134 } else if (ch == TelnetCommand.IAC) {
135 client.sendByte(ch);
136 client.sendByte(TelnetCommand.IAC);
137 } else {
138 client.sendByte(ch);
139 }
140 }
141 }
142 }