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 *      https://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.FilterInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023
024import org.apache.commons.net.util.NetConstants;
025
026/**
027 * This class wraps an input stream, replacing all singly occurring <LF> (linefeed) characters with <CR><LF> (carriage return followed by
028 * linefeed), which is the NETASCII standard for representing a newline. You would use this class to implement ASCII file transfers requiring conversion to
029 * NETASCII.
030 */
031public final class ToNetASCIIInputStream extends FilterInputStream {
032    private static final int NOTHING_SPECIAL = 0;
033    private static final int LAST_WAS_CR = 1;
034    private static final int LAST_WAS_NL = 2;
035    private int status;
036
037    /**
038     * Creates a ToNetASCIIInputStream instance that wraps an existing InputStream.
039     *
040     * @param input The InputStream to wrap.
041     */
042    public ToNetASCIIInputStream(final InputStream input) {
043        super(input);
044        status = NOTHING_SPECIAL;
045    }
046
047    @Override
048    public int available() throws IOException {
049        final int result;
050
051        result = in.available();
052
053        if (status == LAST_WAS_NL) {
054            return result + 1;
055        }
056
057        return result;
058    }
059
060    /** Returns false. Mark is not supported. */
061    @Override
062    public boolean markSupported() {
063        return false;
064    }
065
066    /**
067     * Reads and returns the next byte in the stream. If the end of the message has been reached, returns -1.
068     *
069     * @return The next character in the stream. Returns -1 if the end of the stream has been reached.
070     * @throws IOException If an error occurs while reading the underlying stream.
071     */
072    @Override
073    public int read() throws IOException {
074        final int ch;
075
076        if (status == LAST_WAS_NL) {
077            status = NOTHING_SPECIAL;
078            return '\n';
079        }
080
081        ch = in.read();
082
083        switch (ch) {
084        case '\r':
085            status = LAST_WAS_CR;
086            return '\r';
087        case '\n':
088            if (status != LAST_WAS_CR) {
089                status = LAST_WAS_NL;
090                return '\r';
091            }
092            // falls through$
093        default:
094            status = NOTHING_SPECIAL;
095            return ch;
096        }
097        // statement not reached
098        // return ch;
099    }
100
101    /**
102     * 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.
103     *
104     * @param buffer The byte array in which to store the data.
105     * @return The number of bytes read. Returns -1 if the end of the message has been reached.
106     * @throws IOException If an error occurs in reading the underlying stream.
107     */
108    @Override
109    public int read(final byte[] buffer) throws IOException {
110        return read(buffer, 0, buffer.length);
111    }
112
113    /**
114     * 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.
115     * The characters are stored in the array starting from the given offset and up to the length specified.
116     *
117     * @param buffer The byte array in which to store the data.
118     * @param offset The offset into the array at which to start storing data.
119     * @param length The number of bytes to read.
120     * @return The number of bytes read. Returns -1 if the end of the stream has been reached.
121     * @throws IOException If an error occurs while reading the underlying stream.
122     */
123    @Override
124    public int read(final byte[] buffer, int offset, int length) throws IOException {
125        int ch;
126        final int off;
127
128        if (length < 1) {
129            return 0;
130        }
131
132        ch = available();
133
134        if (length > ch) {
135            length = ch;
136        }
137
138        // If nothing is available, block to read only one character
139        if (length < 1) {
140            length = 1;
141        }
142
143        if ((ch = read()) == NetConstants.EOS) {
144            return NetConstants.EOS;
145        }
146
147        off = offset;
148
149        do {
150            buffer[offset++] = (byte) ch;
151        } while (--length > 0 && (ch = read()) != NetConstants.EOS);
152
153        return offset - off;
154    }
155}