View Javadoc
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    *      http://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 final TelnetClient client;
35      // TODO there does not appear to be any way to change this value - should it be a ctor parameter?
36      private final boolean convertCRtoCRLF = true;
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              if (client.requestedWont(TelnetOption.BINARY)) // i.e. ASCII
96              {
97                  if (lastWasCR) {
98                      if (convertCRtoCRLF) {
99                          client.sendByte('\n');
100                         if (ch == '\n') // i.e. was CRLF anyway
101                         {
102                             lastWasCR = false;
103                             return;
104                         }
105                     } // __convertCRtoCRLF
106                     else if (ch != '\n') {
107                         client.sendByte('\0'); // 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 }