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