    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 &lt;LF&gt; (linefeed) characters with &lt;CR&gt;&lt;LF&gt; (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}