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     * <CR><LF>
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    }