001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.net.io;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.PushbackInputStream;
023import java.nio.charset.StandardCharsets;
024
025import org.apache.commons.net.util.NetConstants;
026
027/**
028 * This class wraps an input stream, replacing all occurrences of <CR><LF> (carriage return followed by a linefeed), which is the NETASCII standard
029 * for representing a newline, with the local line separator representation. You would use this class to implement ASCII file transfers requiring conversion
030 * from NETASCII.
031 *
032 *
033 */
034
035public final class FromNetASCIIInputStream extends PushbackInputStream {
036    static final boolean _noConversionRequired;
037    static final String _lineSeparator;
038    static final byte[] _lineSeparatorBytes;
039
040    static {
041        _lineSeparator = System.lineSeparator();
042        _noConversionRequired = _lineSeparator.equals("\r\n");
043        _lineSeparatorBytes = _lineSeparator.getBytes(StandardCharsets.US_ASCII);
044    }
045
046    /**
047     * 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
048     * whether or not you need to instantiate a FromNetASCIIInputStream object.
049     *
050     * @return True if the NETASCII line separator differs from the local system line separator, false if they are the same.
051     */
052    public static boolean isConversionRequired() {
053        return !_noConversionRequired;
054    }
055
056    private int length;
057
058    /**
059     * Creates a FromNetASCIIInputStream instance that wraps an existing InputStream.
060     *
061     * @param input the stream to wrap
062     */
063    public FromNetASCIIInputStream(final InputStream input) {
064        super(input, _lineSeparatorBytes.length + 1);
065    }
066
067    // PushbackInputStream in JDK 1.1.3 returns the wrong thing
068    // TODO - can we delete this override now?
069    /**
070     * 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
071     * bytes. In other words, you really should not rely on the value returned by this method if you are trying to avoid blocking.
072     */
073    @Override
074    public int available() throws IOException {
075        if (in == null) {
076            throw new IOException("Stream closed");
077        }
078        return buf.length - pos + in.available();
079    }
080
081    /**
082     * 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
083     * multiple reads from the underlying input stream in order to convert NETASCII line separators to the local line separator format. This is transparent to
084     * the programmer and is only mentioned for completeness.
085     *
086     * @return The next character in the stream. Returns -1 if the end of the stream has been reached.
087     * @throws IOException If an error occurs while reading the underlying stream.
088     */
089    @Override
090    public int read() throws IOException {
091        if (_noConversionRequired) {
092            return super.read();
093        }
094
095        return readInt();
096    }
097
098    /**
099     * 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.
100     *
101     * @param buffer The byte array in which to store the data.
102     * @return The number of bytes read. Returns -1 if the end of the message has been reached.
103     * @throws IOException If an error occurs in reading the underlying stream.
104     */
105    @Override
106    public int read(final byte buffer[]) throws IOException {
107        return read(buffer, 0, buffer.length);
108    }
109
110    /**
111     * 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.
112     * The characters are stored in the array starting from the given offset and up to the length specified.
113     *
114     * @param buffer The byte array in which to store the data.
115     * @param offset The offset into the array at which to start storing data.
116     * @param length The number of bytes to read.
117     * @return The number of bytes read. Returns -1 if the end of the stream has been reached.
118     * @throws IOException If an error occurs while reading the underlying stream.
119     */
120    @Override
121    public int read(final byte buffer[], int offset, final int length) throws IOException {
122        if (_noConversionRequired) {
123            return super.read(buffer, offset, length);
124        }
125
126        if (length < 1) {
127            return 0;
128        }
129
130        int ch;
131        final int off;
132
133        ch = available();
134
135        this.length = Math.min(length, ch);
136
137        // If nothing is available, block to read only one character
138        if (this.length < 1) {
139            this.length = 1;
140        }
141
142        if ((ch = readInt()) == -1) {
143            return NetConstants.EOS;
144        }
145
146        off = offset;
147
148        do {
149            buffer[offset++] = (byte) ch;
150        } while (--this.length > 0 && (ch = readInt()) != -1);
151
152        return offset - off;
153    }
154
155    private int readInt() throws IOException {
156        int ch;
157
158        ch = super.read();
159
160        if (ch == '\r') {
161            ch = super.read();
162            if (ch != '\n') {
163                if (ch != -1) {
164                    unread(ch);
165                }
166                return '\r';
167            }
168            unread(_lineSeparatorBytes);
169            ch = super.read();
170            // This is a kluge for read(byte[], ...) to read the right amount
171            --length;
172        }
173
174        return ch;
175    }
176
177}