    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.BufferedReader;
    021import java.io.IOException;
    022import java.io.Reader;
    023
    024/**
    025 * DotTerminatedMessageReader is a class used to read messages from a
    026 * server that are terminated by a single dot followed by a
    027 * &lt;CR&gt;&lt;LF&gt;
    028 * sequence and with double dots appearing at the begining of lines which
    029 * do not signal end of message yet start with a dot.  Various Internet
    030 * protocols such as NNTP and POP3 produce messages of this type.
    031 * <p>
    032 * This class handles stripping of the duplicate period at the beginning
    033 * of lines starting with a period, and ensures you cannot read past the end of the message.
    034 * <p>
    035 * Note: versions since 3.0 extend BufferedReader rather than Reader,
    036 * and no longer change the CRLF into the local EOL. Also only DOT CR LF
    037 * acts as EOF.
    038 */
    039public final class DotTerminatedMessageReader extends BufferedReader
    040{
    041    private static final char LF = '\n';
    042    private static final char CR = '\r';
    043    private static final int DOT = '.';
    044
    045    private boolean atBeginning;
    046    private boolean eof;
    047    private boolean seenCR; // was last character CR?
    048
    049    /**
    050     * Creates a DotTerminatedMessageReader that wraps an existing Reader
    051     * input source.
    052     * @param reader  The Reader input source containing the message.
    053     */
    054    public DotTerminatedMessageReader(final Reader reader)
    055    {
    056        super(reader);
    057        // Assumes input is at start of message
    058        atBeginning = true;
    059        eof = false;
    060    }
    061
    062    /**
    063     * Reads and returns the next character in the message.  If the end of the
    064     * message has been reached, returns -1.  Note that a call to this method
    065     * may result in multiple reads from the underlying input stream to decode
    066     * the message properly (removing doubled dots and so on).  All of
    067     * this is transparent to the programmer and is only mentioned for
    068     * completeness.
    069     * @return The next character in the message. Returns -1 if the end of the
    070     *          message has been reached.
    071     * @throws IOException If an error occurs while reading the underlying
    072     *            stream.
    073     */
    074    @Override
    075    public int read() throws IOException {
    076        synchronized (lock) {
    077            if (eof) {
    078                return -1; // Don't allow read past EOF
    079            }
    080            int chint = super.read();
    081            if (chint == -1) { // True EOF
    082                eof = true;
    083                return -1;
    084            }
    085            if (atBeginning) {
    086                atBeginning = false;
    087                if (chint == DOT) { // Have DOT
    088                    mark(2); // need to check for CR LF or DOT
    089                    chint = super.read();
    090                    if (chint == -1) { // Should not happen
    091                        // new Throwable("Trailing DOT").printStackTrace();
    092                        eof = true;
    093                        return DOT; // return the trailing DOT
    094                    }
    095                    if (chint == DOT) { // Have DOT DOT
    096                        // no need to reset as we want to lose the first DOT
    097                        return chint; // i.e. DOT
    098                    }
    099                    if (chint == CR) { // Have DOT CR
    100                        chint = super.read();
    101                        if (chint == -1) { // Still only DOT CR - should not happen
    102                            //new Throwable("Trailing DOT CR").printStackTrace();
    103                            reset(); // So CR is picked up next time
    104                            return DOT; // return the trailing DOT
    105                        }
    106                        if (chint == LF) { // DOT CR LF
    107                            atBeginning = true;
    108                            eof = true;
    109                            // Do we need to clear the mark somehow?
    110                            return -1;
    111                        }
    112                    }
    113                    // Should not happen - lone DOT at beginning
    114                    //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace();
    115                    reset();
    116                    return DOT;
    117                } // have DOT
    118            } // atBeginning
    119
    120            // Handle CRLF in normal flow
    121            if (seenCR) {
    122                seenCR = false;
    123                if (chint == LF) {
    124                    atBeginning = true;
    125                }
    126            }
    127            if (chint == CR) {
    128                seenCR = true;
    129            }
    130            return chint;
    131        }
    132    }
    133
    134
    135    /**
    136     * Reads the next characters from the message into an array and
    137     * returns the number of characters read.  Returns -1 if the end of the
    138     * message has been reached.
    139     * @param buffer  The character array in which to store the characters.
    140     * @return The number of characters read. Returns -1 if the
    141     *          end of the message has been reached.
    142     * @throws IOException If an error occurs in reading the underlying
    143     *            stream.
    144     */
    145    @Override
    146    public int read(final char[] buffer) throws IOException
    147    {
    148        return read(buffer, 0, buffer.length);
    149    }
    150
    151    /**
    152     * Reads the next characters from the message into an array and
    153     * returns the number of characters read.  Returns -1 if the end of the
    154     * message has been reached.  The characters are stored in the array
    155     * starting from the given offset and up to the length specified.
    156     * @param buffer  The character array in which to store the characters.
    157     * @param offset   The offset into the array at which to start storing
    158     *              characters.
    159     * @param length   The number of characters to read.
    160     * @return The number of characters read. Returns -1 if the
    161     *          end of the message has been reached.
    162     * @throws IOException If an error occurs in reading the underlying
    163     *            stream.
    164     */
    165    @Override
    166    public int read(final char[] buffer, int offset, int length) throws IOException
    167    {
    168        if (length < 1)
    169        {
    170            return 0;
    171        }
    172        int ch;
    173        synchronized (lock)
    174        {
    175            if ((ch = read()) == -1)
    176            {
    177                return -1;
    178            }
    179
    180            final int off = offset;
    181
    182            do
    183            {
    184                buffer[offset++] = (char) ch;
    185            }
    186            while (--length > 0 && (ch = read()) != -1);
    187
    188            return offset - off;
    189        }
    190    }
    191
    192    /**
    193     * Closes the message for reading.  This doesn't actually close the
    194     * underlying stream.  The underlying stream may still be used for
    195     * communicating with the server and therefore is not closed.
    196     * <p>
    197     * If the end of the message has not yet been reached, this method
    198     * will read the remainder of the message until it reaches the end,
    199     * so that the underlying stream may continue to be used properly
    200     * for communicating with the server.  If you do not fully read
    201     * a message, you MUST close it, otherwise your program will likely
    202     * hang or behave improperly.
    203     * @throws IOException  If an error occurs while reading the
    204     *            underlying stream.
    205     */
    206    @Override
    207    public void close() throws IOException
    208    {
    209        synchronized (lock)
    210        {
    211            if (!eof)
    212            {
    213                while (read() != -1)
    214                {
    215                    // read to EOF
    216                }
    217            }
    218            eof = true;
    219            atBeginning = false;
    220        }
    221    }
    222
    223    /**
    224     * Read a line of text.
    225     * A line is considered to be terminated by carriage return followed immediately by a linefeed.
    226     * This contrasts with BufferedReader which also allows other combinations.
    227     * @since 3.0
    228     */
    229    @Override
    230    public String readLine() throws IOException {
    231        final StringBuilder sb = new StringBuilder();
    232        int intch;
    233        synchronized(lock) { // make thread-safe (hopefully!)
    234            while((intch = read()) != -1)
    235            {
    236                if (intch == LF && atBeginning) {
    237                    return sb.substring(0, sb.length()-1);
    238                }
    239                sb.append((char) intch);
    240            }
    241        }
    242        final String string = sb.toString();
    243        if (string.isEmpty()) { // immediate EOF
    244            return null;
    245        }
    246        // Should not happen - EOF without CRLF
    247        //new Throwable(string).printStackTrace();
    248        return string;
    249    }
    250}