001    /*
002     * Copyright 2001-2005 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.net.io;
017    
018    import java.io.IOException;
019    import java.io.PushbackReader;
020    import java.io.Reader;
021    
022    /**
023     * DotTerminatedMessageReader is a class used to read messages from a
024     * server that are terminated by a single dot followed by a
025     * <CR><LF>
026     * sequence and with double dots appearing at the begining of lines which
027     * do not signal end of message yet start with a dot.  Various Internet
028     * protocols such as NNTP and POP3 produce messages of this type.
029     * <p>
030     * This class handles stripping of the duplicate period at the beginning
031     * of lines starting with a period, converts NETASCII newlines to the
032     * local line separator format, truncates the end of message indicator,
033     * and ensures you cannot read past the end of the message.
034     * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
035     * @version $Id: DotTerminatedMessageReader.java 165675 2005-05-02 20:09:55Z rwinston $
036     */
037    public final class DotTerminatedMessageReader extends Reader
038    {
039        private static final String LS;
040        private static final char[] LS_CHARS;
041    
042        static
043        {
044            LS = System.getProperty("line.separator");
045            LS_CHARS = LS.toCharArray();
046        }
047    
048        private boolean atBeginning;
049        private boolean eof;
050        private int pos;
051        private char[] internalBuffer;
052        private PushbackReader internalReader;
053    
054        /**
055         * Creates a DotTerminatedMessageReader that wraps an existing Reader
056         * input source.
057         * @param reader  The Reader input source containing the message.
058         */
059        public DotTerminatedMessageReader(Reader reader)
060        {
061            super(reader);
062            internalBuffer = new char[LS_CHARS.length + 3];
063            pos = internalBuffer.length;
064            // Assumes input is at start of message
065            atBeginning = true;
066            eof = false;
067            internalReader = new PushbackReader(reader);
068        }
069    
070        /**
071         * Reads and returns the next character in the message.  If the end of the
072         * message has been reached, returns -1.  Note that a call to this method
073         * may result in multiple reads from the underlying input stream to decode
074         * the message properly (removing doubled dots and so on).  All of
075         * this is transparent to the programmer and is only mentioned for
076         * completeness.
077         * @return The next character in the message. Returns -1 if the end of the
078         *          message has been reached.
079         * @exception IOException If an error occurs while reading the underlying
080         *            stream.
081         */
082        public int read() throws IOException
083        {
084            int ch;
085    
086            synchronized (lock)
087            {
088                if (pos < internalBuffer.length)
089                {
090                    return internalBuffer[pos++];
091                }
092    
093                if (eof)
094                {
095                    return -1;
096                }
097    
098                if ((ch = internalReader.read()) == -1)
099                {
100                    eof = true;
101                    return -1;
102                }
103    
104                if (atBeginning)
105                {
106                    atBeginning = false;
107                    if (ch == '.')
108                    {
109                        ch = internalReader.read();
110    
111                        if (ch != '.')
112                        {
113                            // read newline
114                            eof = true;
115                            internalReader.read();
116                            return -1;
117                        }
118                        else
119                        {
120                            return '.';
121                        }
122                    }
123                }
124    
125                if (ch == '\r')
126                {
127                    ch = internalReader.read();
128    
129                    if (ch == '\n')
130                    {
131                        ch = internalReader.read();
132    
133                        if (ch == '.')
134                        {
135                            ch = internalReader.read();
136    
137                            if (ch != '.')
138                            {
139                                // read newline and indicate end of file
140                                internalReader.read();
141                                eof = true;
142                            }
143                            else
144                            {
145                                internalBuffer[--pos] = (char) ch;
146                            }
147                        }
148                        else
149                        {
150                            internalReader.unread(ch);
151                        }
152    
153                        pos -= LS_CHARS.length;
154                        System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
155                                         LS_CHARS.length);
156                        ch = internalBuffer[pos++];
157                    }
158                    else
159                    {
160                        internalBuffer[--pos] = (char) ch;
161                        return '\r';
162                    }
163                }
164    
165                return ch;
166            }
167        }
168    
169        /**
170         * Reads the next characters from the message into an array and
171         * returns the number of characters read.  Returns -1 if the end of the
172         * message has been reached.
173         * @param buffer  The character array in which to store the characters.
174         * @return The number of characters read. Returns -1 if the
175         *          end of the message has been reached.
176         * @exception IOException If an error occurs in reading the underlying
177         *            stream.
178         */
179        public int read(char[] buffer) throws IOException
180        {
181            return read(buffer, 0, buffer.length);
182        }
183    
184        /**
185         * Reads the next characters from the message into an array and
186         * returns the number of characters read.  Returns -1 if the end of the
187         * message has been reached.  The characters are stored in the array
188         * starting from the given offset and up to the length specified.
189         * @param buffer  The character array in which to store the characters.
190         * @param offset   The offset into the array at which to start storing
191         *              characters.
192         * @param length   The number of characters to read.
193         * @return The number of characters read. Returns -1 if the
194         *          end of the message has been reached.
195         * @exception IOException If an error occurs in reading the underlying
196         *            stream.
197         */
198        public int read(char[] buffer, int offset, int length) throws IOException
199        {
200            int ch, off;
201            synchronized (lock)
202            {
203                if (length < 1)
204                {
205                    return 0;
206                }
207                if ((ch = read()) == -1)
208                {
209                    return -1;
210                }
211                off = offset;
212    
213                do
214                {
215                    buffer[offset++] = (char) ch;
216                }
217                while (--length > 0 && (ch = read()) != -1);
218    
219                return (offset - off);
220            }
221        }
222    
223        /**
224         * Determines if the message is ready to be read.
225         * @return True if the message is ready to be read, false if not.
226         * @exception IOException If an error occurs while checking the underlying
227         *            stream.
228         */
229        public boolean ready() throws IOException
230        {
231            synchronized (lock)
232            {
233                return (pos < internalBuffer.length || internalReader.ready());
234            }
235        }
236    
237        /**
238         * Closes the message for reading.  This doesn't actually close the
239         * underlying stream.  The underlying stream may still be used for
240         * communicating with the server and therefore is not closed.
241         * <p>
242         * If the end of the message has not yet been reached, this method
243         * will read the remainder of the message until it reaches the end,
244         * so that the underlying stream may continue to be used properly
245         * for communicating with the server.  If you do not fully read
246         * a message, you MUST close it, otherwise your program will likely
247         * hang or behave improperly.
248         * @exception IOException  If an error occurs while reading the
249         *            underlying stream.
250         */
251        public void close() throws IOException
252        {
253            synchronized (lock)
254            {
255                if (internalReader == null)
256                {
257                    return;
258                }
259    
260                if (!eof)
261                {
262                    while (read() != -1)
263                    {
264                        ;
265                    }
266                }
267                eof = true;
268                atBeginning = false;
269                pos = internalBuffer.length;
270                internalReader = null;
271            }
272        }
273    }