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    *      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.io;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.PushbackInputStream;
23  import java.nio.charset.StandardCharsets;
24  
25  import org.apache.commons.net.util.NetConstants;
26  
27  /**
28   * This class wraps an input stream, replacing all occurrences of <CR><LF> (carriage return followed by a linefeed), which is the NETASCII standard
29   * for representing a newline, with the local line separator representation. You would use this class to implement ASCII file transfers requiring conversion
30   * from NETASCII.
31   */
32  public final class FromNetASCIIInputStream extends PushbackInputStream {
33  
34      static final boolean NO_CONVERSION_REQUIRED = System.lineSeparator().equals("\r\n");
35      static final byte[] LINE_SEPARATOR_BYTES = System.lineSeparator().getBytes(StandardCharsets.US_ASCII);
36  
37      /**
38       * Returns true if the NetASCII line separator differs from the system line separator, false if they are the same. This method is useful to determine
39       * whether or not you need to instantiate a FromNetASCIIInputStream object.
40       *
41       * @return True if the NETASCII line separator differs from the local system line separator, false if they are the same.
42       */
43      public static boolean isConversionRequired() {
44          return !NO_CONVERSION_REQUIRED;
45      }
46  
47      private int length;
48  
49      /**
50       * Creates a FromNetASCIIInputStream instance that wraps an existing InputStream.
51       *
52       * @param input the stream to wrap
53       */
54      public FromNetASCIIInputStream(final InputStream input) {
55          super(input, LINE_SEPARATOR_BYTES.length + 1);
56      }
57  
58      // PushbackInputStream in JDK 1.1.3 returns the wrong thing
59      // TODO - can we delete this override now?
60      /**
61       * Returns the number of bytes that can be read without blocking EXCEPT when newline conversions have to be made somewhere within the available block of
62       * bytes. In other words, you really should not rely on the value returned by this method if you are trying to avoid blocking.
63       */
64      @Override
65      public int available() throws IOException {
66          if (in == null) {
67              throw new IOException("Stream closed");
68          }
69          return buf.length - pos + in.available();
70      }
71  
72      /**
73       * Reads and returns the next byte in the stream. If the end of the message has been reached, returns -1. Note that a call to this method may result in
74       * multiple reads from the underlying input stream in order to convert NETASCII line separators to the local line separator format. This is transparent to
75       * the programmer and is only mentioned for completeness.
76       *
77       * @return The next character in the stream. Returns -1 if the end of the stream has been reached.
78       * @throws IOException If an error occurs while reading the underlying stream.
79       */
80      @Override
81      public int read() throws IOException {
82          if (NO_CONVERSION_REQUIRED) {
83              return super.read();
84          }
85          return readInt();
86      }
87  
88      /**
89       * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the stream has been reached.
90       *
91       * @param buffer The byte array in which to store the data.
92       * @return The number of bytes read. Returns -1 if the end of the message has been reached.
93       * @throws IOException If an error occurs in reading the underlying stream.
94       */
95      @Override
96      public int read(final byte buffer[]) throws IOException {
97          return read(buffer, 0, buffer.length);
98      }
99  
100     /**
101      * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the message has been reached.
102      * The characters are stored in the array starting from the given offset and up to the length specified.
103      *
104      * @param buffer The byte array in which to store the data.
105      * @param offset The offset into the array at which to start storing data.
106      * @param length The number of bytes to read.
107      * @return The number of bytes read. Returns -1 if the end of the stream has been reached.
108      * @throws IOException If an error occurs while reading the underlying stream.
109      */
110     @Override
111     public int read(final byte buffer[], int offset, final int length) throws IOException {
112         if (NO_CONVERSION_REQUIRED) {
113             return super.read(buffer, offset, length);
114         }
115         if (length < 1) {
116             return 0;
117         }
118         int ch;
119         final int off;
120         ch = available();
121         this.length = Math.min(length, ch);
122         // If nothing is available, block to read only one character
123         if (this.length < 1) {
124             this.length = 1;
125         }
126         if ((ch = readInt()) == -1) {
127             return NetConstants.EOS;
128         }
129         off = offset;
130         do {
131             buffer[offset++] = (byte) ch;
132         } while (--this.length > 0 && (ch = readInt()) != -1);
133         return offset - off;
134     }
135 
136     private int readInt() throws IOException {
137         int ch;
138         ch = super.read();
139         if (ch == '\r') {
140             ch = super.read();
141             if (ch != '\n') {
142                 if (ch != -1) {
143                     unread(ch);
144                 }
145                 return '\r';
146             }
147             unread(LINE_SEPARATOR_BYTES);
148             ch = super.read();
149             // This is a kluge for read(byte[], ...) to read the right amount
150             --length;
151         }
152         return ch;
153     }
154 
155 }