    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.IOException;
    021    import java.io.PushbackReader;
    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, converts NETASCII newlines to the
    034     * local line separator format, truncates the end of message indicator,
    035     * and ensures you cannot read past the end of the message.
    036     * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
    037     * @version $Id: DotTerminatedMessageReader.java 922748 2010-03-14 03:23:18Z sebb $
    038     */
    039    public final class DotTerminatedMessageReader extends Reader
    040    {
    041        private static final String LS = System.getProperty("line.separator");
    042        char[] LS_CHARS;
    043
    044        private boolean atBeginning;
    045        private boolean eof;
    046        private int pos;
    047        private char[] internalBuffer;
    048        private PushbackReader internalReader;
    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            LS_CHARS = LS.toCharArray();
    059            internalBuffer = new char[LS_CHARS.length + 3];
    060            pos = internalBuffer.length;
    061            // Assumes input is at start of message
    062            atBeginning = true;
    063            eof = false;
    064            internalReader = new PushbackReader(reader);
    065        }
    066
    067        /**
    068         * Reads and returns the next character in the message.  If the end of the
    069         * message has been reached, returns -1.  Note that a call to this method
    070         * may result in multiple reads from the underlying input stream to decode
    071         * the message properly (removing doubled dots and so on).  All of
    072         * this is transparent to the programmer and is only mentioned for
    073         * completeness.
    074         * @return The next character in the message. Returns -1 if the end of the
    075         *          message has been reached.
    076         * @exception IOException If an error occurs while reading the underlying
    077         *            stream.
    078         */
    079        @Override
    080        public int read() throws IOException
    081        {
    082            int ch;
    083
    084            synchronized (lock)
    085            {
    086                if (pos < internalBuffer.length)
    087                {
    088                    return internalBuffer[pos++];
    089                }
    090
    091                if (eof)
    092                {
    093                    return -1;
    094                }
    095
    096                if ((ch = internalReader.read()) == -1)
    097                {
    098                    eof = true;
    099                    return -1;
    100                }
    101
    102                if (atBeginning)
    103                {
    104                    atBeginning = false;
    105                    if (ch == '.')
    106                    {
    107                        ch = internalReader.read();
    108
    109                        if (ch != '.')
    110                        {
    111                            // read newline
    112                            eof = true;
    113                            internalReader.read();
    114                            return -1;
    115                        }
    116                        else
    117                        {
    118                            return '.';
    119                        }
    120                    }
    121                }
    122
    123                if (ch == '\r')
    124                {
    125                    ch = internalReader.read();
    126
    127                    if (ch == '\n')
    128                    {
    129                        ch = internalReader.read();
    130
    131                        if (ch == '.')
    132                        {
    133                            ch = internalReader.read();
    134
    135                            if (ch != '.')
    136                            {
    137                                // read newline and indicate end of file
    138                                internalReader.read();
    139                                eof = true;
    140                            }
    141                            else
    142                            {
    143                                internalBuffer[--pos] = (char) ch;
    144                            }
    145                        }
    146                        else
    147                        {
    148                            internalReader.unread(ch);
    149                        }
    150
    151                        pos -= LS_CHARS.length;
    152                        System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
    153                                         LS_CHARS.length);
    154                        ch = internalBuffer[pos++];
    155                    }
    156                    else if (ch == '\r') {
    157                        internalReader.unread(ch);
    158                    }
    159                    else
    160                    {
    161                        internalBuffer[--pos] = (char) ch;
    162                        return '\r';
    163                    }
    164                }
    165
    166                return ch;
    167            }
    168        }
    169
    170        /**
    171         * Reads the next characters from the message into an array and
    172         * returns the number of characters read.  Returns -1 if the end of the
    173         * message has been reached.
    174         * @param buffer  The character array in which to store the characters.
    175         * @return The number of characters read. Returns -1 if the
    176         *          end of the message has been reached.
    177         * @exception IOException If an error occurs in reading the underlying
    178         *            stream.
    179         */
    180        @Override
    181        public int read(char[] buffer) throws IOException
    182        {
    183            return read(buffer, 0, buffer.length);
    184        }
    185
    186        /**
    187         * Reads the next characters from the message into an array and
    188         * returns the number of characters read.  Returns -1 if the end of the
    189         * message has been reached.  The characters are stored in the array
    190         * starting from the given offset and up to the length specified.
    191         * @param buffer  The character array in which to store the characters.
    192         * @param offset   The offset into the array at which to start storing
    193         *              characters.
    194         * @param length   The number of characters to read.
    195         * @return The number of characters read. Returns -1 if the
    196         *          end of the message has been reached.
    197         * @exception IOException If an error occurs in reading the underlying
    198         *            stream.
    199         */
    200        @Override
    201        public int read(char[] buffer, int offset, int length) throws IOException
    202        {
    203            int ch, off;
    204            synchronized (lock)
    205            {
    206                if (length < 1)
    207                {
    208                    return 0;
    209                }
    210                if ((ch = read()) == -1)
    211                {
    212                    return -1;
    213                }
    214                off = offset;
    215
    216                do
    217                {
    218                    buffer[offset++] = (char) ch;
    219                }
    220                while (--length > 0 && (ch = read()) != -1);
    221
    222                return (offset - off);
    223            }
    224        }
    225
    226        /**
    227         * Determines if the message is ready to be read.
    228         * @return True if the message is ready to be read, false if not.
    229         * @exception IOException If an error occurs while checking the underlying
    230         *            stream.
    231         */
    232        @Override
    233        public boolean ready() throws IOException
    234        {
    235            synchronized (lock)
    236            {
    237                return (pos < internalBuffer.length || internalReader.ready());
    238            }
    239        }
    240
    241        /**
    242         * Closes the message for reading.  This doesn't actually close the
    243         * underlying stream.  The underlying stream may still be used for
    244         * communicating with the server and therefore is not closed.
    245         * <p>
    246         * If the end of the message has not yet been reached, this method
    247         * will read the remainder of the message until it reaches the end,
    248         * so that the underlying stream may continue to be used properly
    249         * for communicating with the server.  If you do not fully read
    250         * a message, you MUST close it, otherwise your program will likely
    251         * hang or behave improperly.
    252         * @exception IOException  If an error occurs while reading the
    253         *            underlying stream.
    254         */
    255        @Override
    256        public void close() throws IOException
    257        {
    258            synchronized (lock)
    259            {
    260                if (internalReader == null)
    261                {
    262                    return;
    263                }
    264
    265                if (!eof)
    266                {
    267                    while (read() != -1)
    268                    {
    269                        // read to EOF
    270                    }
    271                }
    272                eof = true;
    273                atBeginning = false;
    274                pos = internalBuffer.length;
    275                internalReader = null;
    276            }
    277        }
    278    }